From 8bb5588305dd3f31c46be48747912ae972657804 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Thu, 9 Nov 2023 09:46:57 +0800 Subject: [PATCH] v4.6 -1 (#459) --- .github/ISSUE_TEMPLATE/bugs.md | 2 +- .github/ISSUE_TEMPLATE/config.yml | 2 +- README.md | 8 +- .../content/docs/development/configuration.md | 94 +- docSite/content/docs/development/openApi.md | 2 +- docSite/content/docs/installation/one-api.md | 4 +- .../docs/installation/upgrading/447.md | 2 +- .../content/docs/installation/upgrading/46.md | 45 + docSite/content/docs/use-cases/kb.md | 2 +- package.json | 10 +- packages/global/common/bill/constants.ts | 1 - .../global/common/bill/types/billReq.d.ts | 3 - packages/global/common/error/code/app.ts | 28 + packages/global/common/error/code/chat.ts | 23 + packages/global/common/error/code/dataset.ts | 43 + packages/global/common/error/code/openapi.ts | 28 + packages/global/common/error/code/outLink.ts | 34 + packages/global/common/error/code/plugin.ts | 28 + packages/global/common/error/code/team.ts | 22 + packages/global/common/error/code/user.ts | 26 + packages/global/common/error/errorCode.ts | 45 +- packages/global/common/file/constants.ts | 5 + packages/global/common/file/type.d.ts | 8 + packages/global/core/ai/constant.ts | 3 +- packages/global/core/ai/index.ts | 2 + .../global/core/ai}/model.d.ts | 11 +- .../global/core/ai}/model.ts | 79 +- packages/global/core/ai/speech/api.d.ts | 8 + packages/global/core/ai/speech/constant.ts | 17 + packages/global/core/ai/type.d.ts | 20 +- packages/global/core/app/api.d.ts | 18 + packages/global/core/app/constants.ts | 4 + packages/global/core/app/type.d.ts | 32 + packages/global/core/chat/api.d.ts | 34 + .../global/core/chat/constants.ts | 19 +- packages/global/core/chat/type.d.ts | 111 ++ packages/global/core/dataset/constant.ts | 2 + packages/global/core/dataset/type.d.ts | 50 +- packages/global/core/module/api.d.ts | 0 packages/global/core/module/type.d.ts | 13 +- packages/global/core/plugin/type.d.ts | 2 + packages/global/package.json | 2 +- packages/global/support/openapi/type.d.ts | 2 + packages/global/support/outLink/api.d.ts | 27 + packages/global/support/outLink/type.d.ts | 2 + .../global/support/permission/constant.ts | 21 + packages/global/support/permission/type.d.ts | 12 + packages/global/support/permission/utils.ts | 27 + packages/global/support/user/api.d.ts | 15 + packages/global/support/user/constant.ts | 32 +- packages/global/support/user/controller.d.ts | 21 - .../global/support/user/inform/constants.ts | 9 + packages/global/support/user/inform/type.d.ts | 18 + packages/global/support/user/team/constant.ts | 42 + .../global/support/user/team/controller.d.ts | 40 + packages/global/support/user/team/type.d.ts | 46 + packages/global/support/user/type.d.ts | 32 +- packages/global/support/wallet/bill/api.d.ts | 25 + .../global/support/wallet/bill/constants.ts | 16 + .../{common => support/wallet}/bill/tools.ts | 1 + packages/global/support/wallet/bill/type.d.ts | 24 + .../global/support/wallet/{ => pay}/type.d.ts | 2 + packages/service/common/api/plusRequest.ts | 6 +- .../service/common/file/gridfs/controller.ts | 111 ++ packages/service/common/mongo/controller.ts | 26 + packages/service/common/mongo/init.ts | 7 +- packages/service/common/mongo/sessionRun.ts | 48 +- .../service/common/pg/index.ts | 34 +- packages/service/common/pg/type.d.ts | 5 + packages/service/common/response/constant.ts | 6 + packages/service/common/response/index.ts | 94 ++ packages/service/core/ai/audio/speech.ts | 26 + packages/service/core/ai/config.ts | 2 +- .../core/ai/functions/createQuestionGuide.ts | 4 +- packages/service/core/app/schema.ts | 70 + .../service/core/chat/chatItemSchema.ts | 32 +- .../service/core/chat/chatSchema.ts | 34 +- packages/service/core/dataset/auth.ts | 26 - .../service/core/dataset/collection/schema.ts | 16 +- .../service/core/dataset/collection/utils.ts | 6 +- packages/service/core/dataset/controller.ts | 12 + .../service/core/dataset/file/controller.ts | 9 + packages/service/core/dataset/schema.ts | 28 +- .../service/core/dataset/training/schema.ts | 16 +- packages/service/core/plugin/controller.ts | 33 +- packages/service/core/plugin/schema.ts | 15 +- packages/service/package.json | 4 +- packages/service/support/openapi/auth.ts | 10 +- packages/service/support/openapi/schema.ts | 19 +- packages/service/support/outLink/auth.ts | 68 - packages/service/support/outLink/schema.ts | 15 +- .../service/support/permission/auth/app.ts | 72 + .../service/support/permission/auth/chat.ts | 67 + .../service/support/permission/auth/common.ts | 12 + .../support/permission/auth/dataset.ts | 181 +++ .../support/permission/auth/openapi.ts | 61 + .../support/permission/auth/outLink.ts | 98 ++ .../service/support/permission/auth/plugin.ts | 56 + .../service/support/permission/auth/user.ts | 52 + .../service/support/permission/controller.ts | 241 +++ packages/service/support/permission/type.d.ts | 17 + packages/service/support/user/auth.ts | 206 --- packages/service/support/user/controller.ts | 11 + .../service/support/user/inform/controller.ts | 45 - .../service/support/user/inform/schema.ts | 42 - packages/service/support/user/schema.ts | 2 +- .../service/support/user/team/controller.ts | 125 ++ .../service/support/wallet}/bill/schema.ts | 23 +- packages/service/support/wallet/pay/schema.ts | 31 - pnpm-lock.yaml | 1360 ++++++++++++++++- projects/app/data/config.json | 53 +- projects/app/next.config.js | 5 +- projects/app/package.json | 4 +- projects/app/public/docs/chatProblem.md | 8 +- projects/app/public/docs/versionIntro.md | 6 +- projects/app/public/locales/en/common.json | 126 +- projects/app/public/locales/zh/common.json | 120 +- projects/app/src/components/Avatar/index.tsx | 2 +- .../src/components/ChatBox/ContextModal.tsx | 2 +- .../app/src/components/ChatBox/QuoteModal.tsx | 7 +- .../src/components/ChatBox/ResponseTags.tsx | 3 +- .../ChatBox/SelectMarkCollection.tsx | 6 +- .../components/ChatBox/WholeResponseModal.tsx | 4 +- projects/app/src/components/ChatBox/index.tsx | 144 +- .../Icon/icons/common/inviteLight.svg | 11 + .../Icon/icons/common/playLight.svg | 1 + .../components/Icon/icons/common/tickFill.svg | 1 + .../Icon/icons/core/app/ttsFill.svg | 1 + .../Icon/icons/core/chat/sendFill.svg | 5 + .../src/components/Icon/icons/fill/play.svg | 9 +- .../icons/support/permission/privateLight.svg | 8 + .../icons/support/permission/publicLight.svg | 1 + .../Icon/icons/support/team/memberLight.svg | 1 + projects/app/src/components/Icon/index.tsx | 10 +- projects/app/src/components/Layout/index.tsx | 16 +- projects/app/src/components/Layout/navbar.tsx | 2 +- .../src/components/Markdown/chat/Quote.tsx | 2 +- .../Markdown/img/MermaidCodeBlock.tsx | 2 +- projects/app/src/components/MyMenu/index.tsx | 3 +- .../app/src/components/MyTooltip/index.tsx | 1 + projects/app/src/components/Radio/index.tsx | 2 +- projects/app/src/components/Select/index.tsx | 14 +- .../common/Textarea/TagTextarea.tsx | 99 ++ .../core/module/AIChatSettingsModal.tsx | 2 +- .../components/core/module/Flow/ChatTest.tsx | 4 +- .../core/module/Flow/SelectAppModal.tsx | 3 +- .../components/modules/ExtractFieldModal.tsx | 2 +- .../Flow/components/nodes/NodeCQNode.tsx | 2 +- .../Flow/components/nodes/NodeExtract.tsx | 2 +- .../Flow/components/nodes/NodeUserGuide.tsx | 74 +- .../Flow/components/render/RenderInput.tsx | 12 +- .../src/components/support/apikey/Table.tsx | 2 +- .../support/permission/IconText/index.tsx | 20 + .../support/permission/Radio/index.tsx | 38 + .../user/team/TeamManageModal/EditModal.tsx | 159 ++ .../user/team/TeamManageModal/InviteModal.tsx | 106 ++ .../user/team/TeamManageModal/index.tsx | 436 ++++++ .../support/user/team/TeamMenu/index.tsx | 65 + .../user/team/UpdateInviteModal/index.tsx | 133 ++ projects/app/src/constants/app.ts | 38 +- projects/app/src/constants/dataset.ts | 10 +- .../app/src/constants/flow/ModuleTemplate.ts | 19 +- projects/app/src/constants/plugin.ts | 1 - projects/app/src/constants/user.ts | 17 - .../app/src/global/common/api/systemRes.d.ts | 3 +- .../app/src/global/common/tiktoken/index.ts | 2 +- projects/app/src/global/core/ai/api.d.ts | 6 + projects/app/src/global/core/api/aiReq.d.ts | 6 - projects/app/src/global/core/api/appRes.d.ts | 6 - projects/app/src/global/core/api/chatReq.d.ts | 5 - projects/app/src/global/core/api/chatRes.d.ts | 20 - .../app/src/global/core/api/datasetReq.d.ts | 6 +- .../app/src/global/core/api/datasetRes.d.ts | 1 - .../app/src/global/core/app/modules/utils.ts | 9 +- projects/app/src/global/core/chat/api.d.ts | 7 + .../app/src/global/core/dataset/response.d.ts | 2 + .../app/src/global/core/dataset/type.d.ts | 7 - .../src/global/support/api/outLinkRes.d.ts | 6 - .../app/src/global/support/api/userRes.d.ts | 2 +- .../{api/openapiReq.d.ts => openapi/api.d.ts} | 0 .../pages/account/components/BillDetail.tsx | 14 +- .../pages/account/components/BillTable.tsx | 12 +- .../app/src/pages/account/components/Info.tsx | 39 +- .../pages/account/components/InformTable.tsx | 21 +- .../account/components/OpenAIAccountModal.tsx | 2 +- .../src/pages/account/components/PayModal.tsx | 2 +- .../account/components/PayRecordTable.tsx | 6 +- .../pages/account/components/Promotion.tsx | 5 +- .../account/components/UpdatePswModal.tsx | 2 +- projects/app/src/pages/account/index.tsx | 110 +- projects/app/src/pages/api/admin/initv44.ts | 42 - projects/app/src/pages/api/admin/initv441.ts | 35 - projects/app/src/pages/api/admin/initv442.ts | 27 - projects/app/src/pages/api/admin/initv445.ts | 104 -- projects/app/src/pages/api/admin/initv447.ts | 93 -- projects/app/src/pages/api/admin/initv451.ts | 344 ----- projects/app/src/pages/api/admin/initv46.ts | 335 ++++ projects/app/src/pages/api/app/myApps.ts | 32 - .../api/chat/history/updateChatHistory.ts | 37 - projects/app/src/pages/api/chat/init.ts | 110 -- .../app/src/pages/api/chat/removeHistory.ts | 56 - .../pages/api/{system => common}/file/read.ts | 14 +- .../api/{system => common}/file/upload.ts | 25 +- .../{system => common}/file/uploadImage.ts | 6 +- .../system/unlockTask.ts} | 6 +- .../api/core/ai/agent/createQuestionGuide.ts | 22 +- .../src/pages/api/{ => core}/app/create.ts | 23 +- .../api/{ => core}/app/data/totalUsage.ts | 11 +- .../app/src/pages/api/{ => core}/app/del.ts | 24 +- .../src/pages/api/{ => core}/app/detail.tsx | 12 +- .../pages/api/{ => core}/app/getChatLogs.ts | 16 +- projects/app/src/pages/api/core/app/list.ts | 38 + .../src/pages/api/{ => core}/app/update.ts | 28 +- .../src/pages/api/{ => core}/chat/chatTest.ts | 33 +- .../app/src/pages/api/core/chat/delete.ts | 54 + .../{ => core}/chat/feedback/adminUpdate.ts | 13 +- .../{ => core}/chat/feedback/userUpdate.ts | 7 +- projects/app/src/pages/api/core/chat/init.ts | 97 ++ .../chat/item/delete.ts} | 16 +- .../src/pages/api/core/chat/item/getSpeech.ts | 72 + .../getHistory.ts => core/chat/list.ts} | 23 +- .../app/src/pages/api/core/chat/update.ts | 27 + .../src/pages/api/core/dataset/allDataset.ts | 18 +- .../api/core/dataset/collection/create.ts | 43 +- .../api/core/dataset/collection/delById.ts | 38 +- .../api/core/dataset/collection/detail.ts | 27 +- .../pages/api/core/dataset/collection/list.ts | 26 +- .../api/core/dataset/collection/paths.ts | 15 +- .../api/core/dataset/collection/update.ts | 18 +- .../app/src/pages/api/core/dataset/create.ts | 12 +- .../api/core/dataset/data/delDataById.ts | 12 +- .../pages/api/core/dataset/data/exportAll.ts | 20 +- .../api/core/dataset/data/getDataById.ts | 79 +- .../api/core/dataset/data/getDataList.ts | 15 +- .../api/core/dataset/data/getQueueLen.ts | 6 +- .../pages/api/core/dataset/data/insertData.ts | 137 +- .../pages/api/core/dataset/data/pushData.ts | 37 +- .../pages/api/core/dataset/data/updateData.ts | 35 +- .../app/src/pages/api/core/dataset/delete.ts | 27 +- .../app/src/pages/api/core/dataset/detail.ts | 37 +- .../api/core/dataset/file/delEmptyFiles.ts | 31 - .../src/pages/api/core/dataset/file/detail.ts | 53 +- .../api/core/dataset/file/getPreviewUrl.ts | 36 + .../src/pages/api/core/dataset/file/update.ts | 70 - .../app/src/pages/api/core/dataset/list.ts | 21 +- .../app/src/pages/api/core/dataset/paths.ts | 11 +- .../src/pages/api/core/dataset/searchTest.ts | 87 +- .../app/src/pages/api/core/dataset/update.ts | 16 +- .../app/src/pages/api/core/plugin/create.ts | 17 +- .../app/src/pages/api/core/plugin/delete.ts | 14 +- .../app/src/pages/api/core/plugin/detail.ts | 10 +- .../app/src/pages/api/core/plugin/list.ts | 10 +- .../src/pages/api/core/plugin/moduleDetail.ts | 8 +- .../src/pages/api/core/plugin/templateList.ts | 8 +- .../app/src/pages/api/core/plugin/update.ts | 13 +- .../src/pages/api/openapi/plugin/vector.ts | 112 -- .../app/src/pages/api/plugins/urlFetch.ts | 6 +- .../app/src/pages/api/plusApi/[...path].ts | 13 +- .../support/openapi/{postKey.ts => create.ts} | 13 +- .../support/openapi/{delKey.ts => delete.ts} | 8 +- .../src/pages/api/support/openapi/getKeys.ts | 25 - .../app/src/pages/api/support/openapi/list.ts | 48 + .../support/openapi/{putKey.ts => update.ts} | 23 +- .../src/pages/api/support/outLink/create.ts | 15 +- .../src/pages/api/support/outLink/delete.ts | 11 +- .../app/src/pages/api/support/outLink/init.ts | 31 +- .../app/src/pages/api/support/outLink/list.ts | 8 +- .../src/pages/api/support/outLink/update.ts | 9 +- .../wallet}/bill/createTrainingBill.ts | 20 +- .../app/src/pages/api/system/file/delete.ts | 30 - .../app/src/pages/api/system/file/readUrl.ts | 75 - .../app/src/pages/api/system/getInitData.ts | 30 +- projects/app/src/pages/api/system/img/[id].ts | 2 +- .../pages/api/user/account/loginByPassword.ts | 19 +- .../src/pages/api/user/account/loginout.ts | 4 +- .../src/pages/api/user/account/tokenLogin.ts | 17 +- .../app/src/pages/api/user/account/update.ts | 6 +- .../api/user/account/updatePasswordByOld.ts | 6 +- projects/app/src/pages/api/user/getBill.ts | 57 - .../app/src/pages/api/user/getPayOrders.ts | 29 - .../src/pages/api/user/inform/countUnread.ts | 31 - .../app/src/pages/api/user/inform/list.ts | 40 - .../app/src/pages/api/user/inform/read.ts | 29 - .../app/src/pages/api/user/inform/send.ts | 55 - .../api/user/promotion/getPromotionData.ts | 53 - .../pages/api/user/promotion/getPromotions.ts | 43 - .../app/src/pages/api/v1/chat/completions.ts | 465 ++---- .../app/src/pages/api/v1/chat/getHistory.ts | 25 +- projects/app/src/pages/api/v1/embeddings.ts | 58 + .../app/detail/components/AdEdit/Header.tsx | 10 +- .../app/detail/components/AdEdit/index.tsx | 2 +- .../app/detail/components/BasicEdit/index.tsx | 161 +- .../detail/components/Charts/TotalUsage.tsx | 2 +- .../pages/app/detail/components/InfoModal.tsx | 27 +- .../src/pages/app/detail/components/Logs.tsx | 4 +- .../app/detail/components/OutLink/Share.tsx | 6 +- .../pages/app/detail/components/QGSwitch.tsx | 23 + .../pages/app/detail/components/TTSSelect.tsx | 94 ++ projects/app/src/pages/app/detail/index.tsx | 5 +- .../pages/app/list/component/CreateModal.tsx | 4 +- projects/app/src/pages/app/list/index.tsx | 172 ++- .../src/pages/chat/components/ChatHeader.tsx | 2 +- .../chat/components/ChatHistorySlider.tsx | 18 +- .../src/pages/chat/components/SliderApps.tsx | 4 +- .../src/pages/chat/components/ToolMenu.tsx | 2 +- projects/app/src/pages/chat/index.tsx | 8 +- projects/app/src/pages/chat/share.tsx | 2 +- .../detail/components/CollectionCard.tsx | 275 ++-- .../dataset/detail/components/DataCard.tsx | 112 +- .../detail/components/Import/Chunk.tsx | 2 +- .../detail/components/Import/FileSelect.tsx | 27 +- .../detail/components/Import/ImportModal.tsx | 2 +- .../detail/components/Import/Provider.tsx | 28 +- .../dataset/detail/components/Import/QA.tsx | 2 +- .../pages/dataset/detail/components/Info.tsx | 50 +- .../detail/components/InputDataModal.tsx | 28 +- .../pages/dataset/detail/components/Test.tsx | 4 +- .../app/src/pages/dataset/detail/index.tsx | 24 +- .../dataset/list/component/CreateModal.tsx | 9 +- projects/app/src/pages/dataset/list/index.tsx | 307 ++-- .../src/pages/login/components/LoginForm.tsx | 4 +- .../pages/plugin/list/component/EditModal.tsx | 4 +- .../common/censor/index.ts} | 0 projects/app/src/service/common/tiktoken.ts | 4 +- projects/app/src/service/core/ai/model.ts | 12 +- projects/app/src/service/core/ai/vector.ts | 70 + .../service/core/dataset/data/controller.ts | 168 ++ .../src/service/core/dataset/data/utils.ts | 155 +- projects/app/src/service/events/generateQA.ts | 174 ++- .../app/src/service/events/generateVector.ts | 196 +-- projects/app/src/service/events/sendInform.ts | 16 - projects/app/src/service/lib/gridfs.ts | 125 -- projects/app/src/service/models/app.ts | 74 - .../moduleDispatch/agent/classifyQuestion.ts | 10 +- .../service/moduleDispatch/agent/extract.ts | 10 +- .../src/service/moduleDispatch/chat/oneapi.ts | 22 +- .../service/moduleDispatch/dataset/search.ts | 50 +- .../app/src/service/moduleDispatch/index.ts | 315 +++- .../service/moduleDispatch/init/history.tsx | 2 +- .../src/service/moduleDispatch/plugin/run.ts | 23 +- .../moduleDispatch/plugin/runOutput.ts | 4 +- .../service/moduleDispatch/tools/answer.ts | 3 +- .../src/service/moduleDispatch/tools/http.ts | 10 +- .../service/moduleDispatch/tools/runApp.ts | 24 +- projects/app/src/service/mongo.ts | 28 +- projects/app/src/service/response.ts | 96 -- .../app/src/service/support/outLink/auth.ts | 14 + .../service/support/permission/auth/bill.ts | 8 + .../support/permission/auth/dataset.ts | 36 + .../support/permission/auth/outLink.ts | 31 + .../service/support/permission/auth/user.ts | 48 + .../src/service/support/user/controller.ts | 30 + .../src/service/support/user/inform/api.ts | 7 + .../{common => support/wallet}/bill/push.ts | 164 +- .../src/service/support/wallet/bill/utils.ts | 32 + projects/app/src/service/utils/auth.ts | 49 - .../app/src/service/utils/chat/saveChat.ts | 48 +- projects/app/src/service/utils/tools.ts | 27 - projects/app/src/types/app.d.ts | 53 +- projects/app/src/types/chat.d.ts | 83 - projects/app/src/types/common/bill.d.ts | 23 - projects/app/src/types/core/chat/type.d.ts | 10 +- .../app/src/types/core/dataset/index.d.ts | 14 +- projects/app/src/types/i18n.d.ts | 13 +- projects/app/src/types/index.d.ts | 9 +- projects/app/src/types/mongoSchema.d.ts | 64 - projects/app/src/types/user.d.ts | 23 +- projects/app/src/utils/adapt.ts | 32 +- .../app/src/utils/common/adapt/message.ts | 17 +- .../app/src/utils/service/core/chat/index.ts | 2 +- projects/app/src/web/common/api/fetch.ts | 5 +- projects/app/src/web/common/bill/api.ts | 34 - projects/app/src/web/common/file/api.ts | 17 + .../app/src/web/common/file/controller.ts | 104 ++ projects/app/src/web/common/file/utils.ts | 107 +- .../app/src/web/common/hooks/useConfirm.tsx | 30 +- .../app/src/web/common/hooks/useCopyData.tsx | 2 +- projects/app/src/web/common/system/api.ts | 18 - .../app/src/web/common/system/staticData.ts | 2 +- .../src/web/common/system/useSystemStore.ts | 2 +- projects/app/src/web/common/utils/voice.ts | 124 +- projects/app/src/web/core/ai/api.ts | 2 +- projects/app/src/web/core/app/api.ts | 23 +- .../app/src/web/core/app/basicSettings.ts | 51 +- .../app/src/web/core/app/store/useAppStore.ts | 65 + projects/app/src/web/core/chat/api.ts | 32 +- projects/app/src/web/core/chat/storeChat.ts | 7 +- .../app/src/web/core/chat/storeShareChat.ts | 9 +- projects/app/src/web/core/dataset/api.ts | 25 +- .../app/src/web/core/dataset/store/dataset.ts | 16 +- projects/app/src/web/core/dataset/utils.ts | 8 +- projects/app/src/web/styles/theme.ts | 4 +- .../src/web/support/activity/promotion/api.ts | 14 + projects/app/src/web/support/openapi/api.ts | 12 +- projects/app/src/web/support/outLink/api.ts | 2 +- projects/app/src/web/support/user/api.ts | 38 +- projects/app/src/web/support/user/auth.ts | 2 + .../app/src/web/support/user/inform/api.ts | 9 + projects/app/src/web/support/user/team/api.ts | 40 + .../app/src/web/support/user/useUserStore.ts | 55 +- .../app/src/web/support/wallet/bill/api.ts | 10 + .../app/src/web/support/wallet/pay/api.ts | 25 + 402 files changed, 9899 insertions(+), 5967 deletions(-) create mode 100644 docSite/content/docs/installation/upgrading/46.md delete mode 100644 packages/global/common/bill/constants.ts delete mode 100644 packages/global/common/bill/types/billReq.d.ts create mode 100644 packages/global/common/error/code/app.ts create mode 100644 packages/global/common/error/code/chat.ts create mode 100644 packages/global/common/error/code/dataset.ts create mode 100644 packages/global/common/error/code/openapi.ts create mode 100644 packages/global/common/error/code/outLink.ts create mode 100644 packages/global/common/error/code/plugin.ts create mode 100644 packages/global/common/error/code/team.ts create mode 100644 packages/global/common/error/code/user.ts create mode 100644 packages/global/common/file/constants.ts create mode 100644 packages/global/common/file/type.d.ts create mode 100644 packages/global/core/ai/index.ts rename {projects/app/src/types => packages/global/core/ai}/model.d.ts (78%) rename {projects/app/src/constants => packages/global/core/ai}/model.ts (59%) create mode 100644 packages/global/core/ai/speech/api.d.ts create mode 100644 packages/global/core/ai/speech/constant.ts create mode 100644 packages/global/core/app/api.d.ts create mode 100644 packages/global/core/app/constants.ts create mode 100644 packages/global/core/app/type.d.ts create mode 100644 packages/global/core/chat/api.d.ts rename projects/app/src/constants/chat.ts => packages/global/core/chat/constants.ts (80%) create mode 100644 packages/global/core/chat/type.d.ts create mode 100644 packages/global/core/module/api.d.ts create mode 100644 packages/global/support/outLink/api.d.ts create mode 100644 packages/global/support/permission/constant.ts create mode 100644 packages/global/support/permission/type.d.ts create mode 100644 packages/global/support/permission/utils.ts create mode 100644 packages/global/support/user/api.d.ts delete mode 100644 packages/global/support/user/controller.d.ts create mode 100644 packages/global/support/user/inform/constants.ts create mode 100644 packages/global/support/user/inform/type.d.ts create mode 100644 packages/global/support/user/team/constant.ts create mode 100644 packages/global/support/user/team/controller.d.ts create mode 100644 packages/global/support/user/team/type.d.ts create mode 100644 packages/global/support/wallet/bill/api.d.ts create mode 100644 packages/global/support/wallet/bill/constants.ts rename packages/global/{common => support/wallet}/bill/tools.ts (82%) create mode 100644 packages/global/support/wallet/bill/type.d.ts rename packages/global/support/wallet/{ => pay}/type.d.ts (83%) create mode 100644 packages/service/common/file/gridfs/controller.ts create mode 100644 packages/service/common/mongo/controller.ts rename projects/app/src/service/pg.ts => packages/service/common/pg/index.ts (88%) create mode 100644 packages/service/common/pg/type.d.ts create mode 100644 packages/service/common/response/constant.ts create mode 100644 packages/service/core/ai/audio/speech.ts create mode 100644 packages/service/core/app/schema.ts rename projects/app/src/service/models/chatItem.ts => packages/service/core/chat/chatItemSchema.ts (61%) rename projects/app/src/service/models/chat.ts => packages/service/core/chat/chatSchema.ts (61%) delete mode 100644 packages/service/core/dataset/auth.ts create mode 100644 packages/service/core/dataset/controller.ts create mode 100644 packages/service/core/dataset/file/controller.ts delete mode 100644 packages/service/support/outLink/auth.ts create mode 100644 packages/service/support/permission/auth/app.ts create mode 100644 packages/service/support/permission/auth/chat.ts create mode 100644 packages/service/support/permission/auth/common.ts create mode 100644 packages/service/support/permission/auth/dataset.ts create mode 100644 packages/service/support/permission/auth/openapi.ts create mode 100644 packages/service/support/permission/auth/outLink.ts create mode 100644 packages/service/support/permission/auth/plugin.ts create mode 100644 packages/service/support/permission/auth/user.ts create mode 100644 packages/service/support/permission/controller.ts create mode 100644 packages/service/support/permission/type.d.ts delete mode 100644 packages/service/support/user/auth.ts create mode 100644 packages/service/support/user/controller.ts delete mode 100644 packages/service/support/user/inform/controller.ts delete mode 100644 packages/service/support/user/inform/schema.ts create mode 100644 packages/service/support/user/team/controller.ts rename {projects/app/src/service/common => packages/service/support/wallet}/bill/schema.ts (53%) delete mode 100644 packages/service/support/wallet/pay/schema.ts create mode 100644 projects/app/src/components/Icon/icons/common/inviteLight.svg create mode 100644 projects/app/src/components/Icon/icons/common/playLight.svg create mode 100644 projects/app/src/components/Icon/icons/common/tickFill.svg create mode 100644 projects/app/src/components/Icon/icons/core/app/ttsFill.svg create mode 100644 projects/app/src/components/Icon/icons/core/chat/sendFill.svg create mode 100644 projects/app/src/components/Icon/icons/support/permission/privateLight.svg create mode 100644 projects/app/src/components/Icon/icons/support/permission/publicLight.svg create mode 100644 projects/app/src/components/Icon/icons/support/team/memberLight.svg create mode 100644 projects/app/src/components/common/Textarea/TagTextarea.tsx create mode 100644 projects/app/src/components/support/permission/IconText/index.tsx create mode 100644 projects/app/src/components/support/permission/Radio/index.tsx create mode 100644 projects/app/src/components/support/user/team/TeamManageModal/EditModal.tsx create mode 100644 projects/app/src/components/support/user/team/TeamManageModal/InviteModal.tsx create mode 100644 projects/app/src/components/support/user/team/TeamManageModal/index.tsx create mode 100644 projects/app/src/components/support/user/team/TeamMenu/index.tsx create mode 100644 projects/app/src/components/support/user/team/UpdateInviteModal/index.tsx delete mode 100644 projects/app/src/constants/plugin.ts create mode 100644 projects/app/src/global/core/ai/api.d.ts delete mode 100644 projects/app/src/global/core/api/aiReq.d.ts delete mode 100644 projects/app/src/global/core/api/appRes.d.ts delete mode 100644 projects/app/src/global/core/api/chatReq.d.ts delete mode 100644 projects/app/src/global/core/api/chatRes.d.ts create mode 100644 projects/app/src/global/core/chat/api.d.ts delete mode 100644 projects/app/src/global/core/dataset/type.d.ts delete mode 100644 projects/app/src/global/support/api/outLinkRes.d.ts rename projects/app/src/global/support/{api/openapiReq.d.ts => openapi/api.d.ts} (100%) delete mode 100644 projects/app/src/pages/api/admin/initv44.ts delete mode 100644 projects/app/src/pages/api/admin/initv441.ts delete mode 100644 projects/app/src/pages/api/admin/initv442.ts delete mode 100644 projects/app/src/pages/api/admin/initv445.ts delete mode 100644 projects/app/src/pages/api/admin/initv447.ts delete mode 100644 projects/app/src/pages/api/admin/initv451.ts create mode 100644 projects/app/src/pages/api/admin/initv46.ts delete mode 100644 projects/app/src/pages/api/app/myApps.ts delete mode 100644 projects/app/src/pages/api/chat/history/updateChatHistory.ts delete mode 100644 projects/app/src/pages/api/chat/init.ts delete mode 100644 projects/app/src/pages/api/chat/removeHistory.ts rename projects/app/src/pages/api/{system => common}/file/read.ts (69%) rename projects/app/src/pages/api/{system => common}/file/upload.ts (75%) rename projects/app/src/pages/api/{system => common}/file/uploadImage.ts (74%) rename projects/app/src/pages/api/{user/account/paySuccess.ts => common/system/unlockTask.ts} (77%) rename projects/app/src/pages/api/{ => core}/app/create.ts (57%) rename projects/app/src/pages/api/{ => core}/app/data/totalUsage.ts (74%) rename projects/app/src/pages/api/{ => core}/app/del.ts (59%) rename projects/app/src/pages/api/{ => core}/app/detail.tsx (64%) rename projects/app/src/pages/api/{ => core}/app/getChatLogs.ts (85%) create mode 100644 projects/app/src/pages/api/core/app/list.ts rename projects/app/src/pages/api/{ => core}/app/update.ts (62%) rename projects/app/src/pages/api/{ => core}/chat/chatTest.ts (68%) create mode 100644 projects/app/src/pages/api/core/chat/delete.ts rename projects/app/src/pages/api/{ => core}/chat/feedback/adminUpdate.ts (61%) rename projects/app/src/pages/api/{ => core}/chat/feedback/userUpdate.ts (75%) create mode 100644 projects/app/src/pages/api/core/chat/init.ts rename projects/app/src/pages/api/{chat/delChatRecordByContentId.ts => core/chat/item/delete.ts} (52%) create mode 100644 projects/app/src/pages/api/core/chat/item/getSpeech.ts rename projects/app/src/pages/api/{chat/history/getHistory.ts => core/chat/list.ts} (52%) create mode 100644 projects/app/src/pages/api/core/chat/update.ts delete mode 100644 projects/app/src/pages/api/core/dataset/file/delEmptyFiles.ts create mode 100644 projects/app/src/pages/api/core/dataset/file/getPreviewUrl.ts delete mode 100644 projects/app/src/pages/api/core/dataset/file/update.ts delete mode 100644 projects/app/src/pages/api/openapi/plugin/vector.ts rename projects/app/src/pages/api/support/openapi/{postKey.ts => create.ts} (72%) rename projects/app/src/pages/api/support/openapi/{delKey.ts => delete.ts} (68%) delete mode 100644 projects/app/src/pages/api/support/openapi/getKeys.ts create mode 100644 projects/app/src/pages/api/support/openapi/list.ts rename projects/app/src/pages/api/support/openapi/{putKey.ts => update.ts} (54%) rename projects/app/src/pages/api/{common => support/wallet}/bill/createTrainingBill.ts (53%) delete mode 100644 projects/app/src/pages/api/system/file/delete.ts delete mode 100644 projects/app/src/pages/api/system/file/readUrl.ts delete mode 100644 projects/app/src/pages/api/user/getBill.ts delete mode 100644 projects/app/src/pages/api/user/getPayOrders.ts delete mode 100644 projects/app/src/pages/api/user/inform/countUnread.ts delete mode 100644 projects/app/src/pages/api/user/inform/list.ts delete mode 100644 projects/app/src/pages/api/user/inform/read.ts delete mode 100644 projects/app/src/pages/api/user/inform/send.ts delete mode 100644 projects/app/src/pages/api/user/promotion/getPromotionData.ts delete mode 100644 projects/app/src/pages/api/user/promotion/getPromotions.ts create mode 100644 projects/app/src/pages/api/v1/embeddings.ts create mode 100644 projects/app/src/pages/app/detail/components/QGSwitch.tsx create mode 100644 projects/app/src/pages/app/detail/components/TTSSelect.tsx rename projects/app/src/{web/common/plusApi/censor.ts => service/common/censor/index.ts} (100%) create mode 100644 projects/app/src/service/core/ai/vector.ts create mode 100644 projects/app/src/service/core/dataset/data/controller.ts delete mode 100644 projects/app/src/service/events/sendInform.ts delete mode 100644 projects/app/src/service/lib/gridfs.ts delete mode 100644 projects/app/src/service/models/app.ts delete mode 100644 projects/app/src/service/response.ts create mode 100644 projects/app/src/service/support/outLink/auth.ts create mode 100644 projects/app/src/service/support/permission/auth/bill.ts create mode 100644 projects/app/src/service/support/permission/auth/dataset.ts create mode 100644 projects/app/src/service/support/permission/auth/outLink.ts create mode 100644 projects/app/src/service/support/permission/auth/user.ts create mode 100644 projects/app/src/service/support/user/controller.ts create mode 100644 projects/app/src/service/support/user/inform/api.ts rename projects/app/src/service/{common => support/wallet}/bill/push.ts (52%) create mode 100644 projects/app/src/service/support/wallet/bill/utils.ts delete mode 100644 projects/app/src/service/utils/auth.ts delete mode 100644 projects/app/src/types/common/bill.d.ts delete mode 100644 projects/app/src/types/mongoSchema.d.ts delete mode 100644 projects/app/src/web/common/bill/api.ts create mode 100644 projects/app/src/web/common/file/api.ts create mode 100644 projects/app/src/web/common/file/controller.ts create mode 100644 projects/app/src/web/core/app/store/useAppStore.ts create mode 100644 projects/app/src/web/support/activity/promotion/api.ts create mode 100644 projects/app/src/web/support/user/inform/api.ts create mode 100644 projects/app/src/web/support/user/team/api.ts create mode 100644 projects/app/src/web/support/wallet/bill/api.ts create mode 100644 projects/app/src/web/support/wallet/pay/api.ts diff --git a/.github/ISSUE_TEMPLATE/bugs.md b/.github/ISSUE_TEMPLATE/bugs.md index 75378b012..8aa370dcf 100644 --- a/.github/ISSUE_TEMPLATE/bugs.md +++ b/.github/ISSUE_TEMPLATE/bugs.md @@ -11,7 +11,7 @@ assignees: '' [//]: # '方框内填 x 表示打钩' - [ ] 我已确认目前没有类似 issue -- [ ] 我已完整查看过项目 README,以及[项目文档](https://doc.fastgpt.run/docs/intro/) +- [ ] 我已完整查看过项目 README,以及[项目文档](https://doc.fastgpt.in/docs/intro/) - [ ] 我使用了自己的 key,并确认我的 key 是可正常使用的 - [ ] 我理解并愿意跟进此 issue,协助测试和提供反馈 - [x] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 issue 可能会被无视或直接关闭** diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 068487e45..1cedc8a78 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: false contact_links: - name: 微信交流群 - url: https://doc.fastgpt.run/wechat-fastgpt.webp + url: https://doc.fastgpt.in/wechat-fastgpt.webp about: FastGPT 全是问题群 diff --git a/README.md b/README.md index fc59db621..c8a699fcb 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,10 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开 cloud - + document - + development @@ -119,7 +119,7 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b ## 🤝 第三方生态 -- [OnWeChat 个人微信/企微机器人](https://doc.fastgpt.run/docs/use-cases/onwechat/) +- [OnWeChat 个人微信/企微机器人](https://doc.fastgpt.in/docs/use-cases/onwechat/) ## 🌟 Star History @@ -132,4 +132,4 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b 1. 允许作为后台服务直接商用,但不允许提供 SaaS 服务。 2. 未经商业授权,任何形式的商用服务均需保留相关版权信息。 3. 完整请查看 [FastGPT Open Source License](./LICENSE) -4. 联系方式:yujinlong@sealos.io,[点击查看商业版定价策略](https://doc.fastgpt.run/docs/commercial) +4. 联系方式:yujinlong@sealos.io,[点击查看商业版定价策略](https://doc.fastgpt.in/docs/commercial) diff --git a/docSite/content/docs/development/configuration.md b/docSite/content/docs/development/configuration.md index 101f7511a..971ed48f5 100644 --- a/docSite/content/docs/development/configuration.md +++ b/docSite/content/docs/development/configuration.md @@ -21,62 +21,61 @@ weight: 520 ```json { "SystemParams": { + "pluginBaseUrl": "", // 商业版接口地址 "vectorMaxProcess": 15, // 向量生成最大进程,结合数据库性能和 key 来设置 "qaMaxProcess": 15, // QA 生成最大进程,结合数据库性能和 key 来设置 "pgHNSWEfSearch": 100 // pg vector 索引参数,越大精度高但速度慢 }, "ChatModels": [ { - "model": "gpt-3.5-turbo", // 实际调用的模型 - "name": "GPT35-4k", // 展示的名字 - "maxToken": 4000, // 最大token,均按 gpt35 计算 - "quoteMaxToken": 2000, // 引用内容最大 token - "maxTemperature": 1.2, // 最大温度 - "price": 0, + "model": "gpt-3.5-turbo-1106", + "name": "GPT35-1106", + "price": 0, // 除以 100000 后等于1个token的价格 + "maxContext": 16000, // 最大上下文长度 + "maxResponse": 4000, // 最大回复长度 + "quoteMaxToken": 2000, // 最大引用内容长度 + "maxTemperature": 1.2, // 最大温度值 + "censor": false, // 是否开启敏感词过滤(商业版) "defaultSystemChatPrompt": "" }, { "model": "gpt-3.5-turbo-16k", "name": "GPT35-16k", - "maxToken": 16000, + "maxContext": 16000, + "maxResponse": 16000, + "price": 0, "quoteMaxToken": 8000, "maxTemperature": 1.2, - "price": 0, + "censor": false, "defaultSystemChatPrompt": "" }, { "model": "gpt-4", "name": "GPT4-8k", - "maxToken": 8000, + "maxContext": 8000, + "maxResponse": 8000, + "price": 0, "quoteMaxToken": 4000, "maxTemperature": 1.2, - "price": 0, + "censor": false, "defaultSystemChatPrompt": "" } ], - "QAModels": [ // QA 拆分模型 - { + "QAModels": [ + { "model": "gpt-3.5-turbo-16k", "name": "GPT35-16k", - "maxToken": 16000, + "maxContext": 16000, + "maxResponse": 16000, "price": 0 } ], - "ExtractModels": [ // 内容提取模型 - { - "model": "gpt-3.5-turbo-16k", - "name": "GPT35-16k", - "maxToken": 16000, - "price": 0, - "functionCall": true, // 是否支持 function call - "functionPrompt": "" // 自定义非 function call 提示词 - } - ], - "CQModels": [ // Classify Question: 问题分类模型 + "CQModels": [ { - "model": "gpt-3.5-turbo-16k", - "name": "GPT35-16k", - "maxToken": 16000, + "model": "gpt-3.5-turbo-1106", + "name": "GPT35-1106", + "maxContext": 16000, + "maxResponse": 4000, "price": 0, "functionCall": true, "functionPrompt": "" @@ -84,17 +83,30 @@ weight: 520 { "model": "gpt-4", "name": "GPT4-8k", - "maxToken": 8000, + "maxContext": 8000, + "maxResponse": 8000, "price": 0, "functionCall": true, "functionPrompt": "" } ], - "QGModels": [ // Question Generation: 生成下一步指引模型 - { - "model": "gpt-3.5-turbo", - "name": "GPT35-4k", - "maxToken": 4000, + "ExtractModels": [ + { + "model": "gpt-3.5-turbo-1106", + "name": "GPT35-1106", + "maxContext": 16000, + "maxResponse": 4000, + "price": 0, + "functionCall": true, + "functionPrompt": "" + } + ], + "QGModels": [ + { + "model": "gpt-3.5-turbo-1106", + "name": "GPT35-1106", + "maxContext": 1600, + "maxResponse": 4000, "price": 0 } ], @@ -102,10 +114,22 @@ weight: 520 { "model": "text-embedding-ada-002", "name": "Embedding-2", - "price": 0, - "defaultToken": 500, + "price": 0.2, + "defaultToken": 700, "maxToken": 3000 } + ], + "AudioSpeechModels": [ + { + "model": "tts-1", + "name": "OpenAI TTS1", + "price": 0 + }, + { + "model": "tts-1-hd", + "name": "OpenAI TTS1HD", + "price": 0 + } ] } ``` diff --git a/docSite/content/docs/development/openApi.md b/docSite/content/docs/development/openApi.md index 09866425d..8b49041f5 100644 --- a/docSite/content/docs/development/openApi.md +++ b/docSite/content/docs/development/openApi.md @@ -393,7 +393,7 @@ curl --location --request POST 'https://fastgpt.run/api/core/dataset/searchTest' **请求示例** ```bash -curl --location --request POST 'https://fastgpt.run/api/common/bill/createTrainingBill' \ +curl --location --request POST 'https://fastgpt.run/api/support/wallet/bill/createTrainingBill' \ --header 'Authorization: Bearer {{apikey}}' \ --header 'Content-Type: application/json' \ --data-raw '' diff --git a/docSite/content/docs/installation/one-api.md b/docSite/content/docs/installation/one-api.md index 7513b65a0..d06ec0827 100644 --- a/docSite/content/docs/installation/one-api.md +++ b/docSite/content/docs/installation/one-api.md @@ -99,9 +99,9 @@ CHAT_API_KEY=sk-xxxxxx { "model": "ERNIE-Bot", // 这里的模型需要对应 One API 的模型 "name": "文心一言", // 对外展示的名称 - "maxToken": 4000, // 最大长下文 token,无论什么模型都按 GPT35 的计算。GPT 外的模型需要自行大致计算下这个值。可以调用官方接口去比对 Token 的倍率,然后在这里粗略计算。 + "maxContext": 8000, // 最大长下文 token,无论什么模型都按 GPT35 的计算。GPT 外的模型需要自行大致计算下这个值。可以调用官方接口去比对 Token 的倍率,然后在这里粗略计算。 + "maxResponse": 4000, // 最大回复 token // 例如:文心一言的中英文 token 基本是 1:1,而 GPT 的中文 Token 是 2:1,如果文心一言官方最大 Token 是 4000,那么这里就可以填 8000,保险点就填 7000. - "price": 0, // 1个token 价格 => 1.5 / 100000 * 1000 = 0.015元/1k token "quoteMaxToken": 2000, // 引用知识库的最大 Token "maxTemperature": 1, // 最大温度 "defaultSystemChatPrompt": "" // 默认的系统提示词 diff --git a/docSite/content/docs/installation/upgrading/447.md b/docSite/content/docs/installation/upgrading/447.md index 05ceb9ddb..c18c59db5 100644 --- a/docSite/content/docs/installation/upgrading/447.md +++ b/docSite/content/docs/installation/upgrading/447.md @@ -1,5 +1,5 @@ --- -title: 'V4.4.7' +title: 'V4.4.7(需执行升级脚本)' description: 'FastGPT V4.4.7 更新(需执行升级脚本)' icon: 'upgrade' draft: false diff --git a/docSite/content/docs/installation/upgrading/46.md b/docSite/content/docs/installation/upgrading/46.md new file mode 100644 index 000000000..8012a33c6 --- /dev/null +++ b/docSite/content/docs/installation/upgrading/46.md @@ -0,0 +1,45 @@ +--- +title: 'V4.6(需要初始化)' +description: 'FastGPT V4.6 更新' +icon: 'upgrade' +draft: false +toc: true +weight: 837 +--- + +未正式发布。 + +V4.6 版本加入了简单的团队功能,可以邀请其他用户进来管理资源。该版本升级后无法执行旧的升级脚本,且无法回退。 + +## 1. 更新镜像并变更配置文件 + +更新镜像至 latest 或者 v4.6 版本。商业版镜像更新至 V0.2. + +最新配置可参考: [V46版本最新 config.json](/docs/development/configuration),商业镜像配置文件也更新,参考最新的飞书文档。 + + +## 2. 执行初始化 API + +发起 1 个 HTTP 请求({{rootkey}} 替换成环境变量里的`rootkey`,{{host}}替换成自己域名) + +1. https://xxxxx/api/admin/initv46 + +```bash +curl --location --request POST 'https://{{host}}/api/admin/initv46' \ +--header 'rootkey: {{rootkey}}' \ +--header 'Content-Type: application/json' +``` + +初始化内容: +1. 创建默认团队 +2. 初始化 Mongo 所有资源的团队字段 +3. 初始化 Pg 的字段 + +**该初始化接口可能速度很慢,返回超时不用管,注意看日志即可** + + +## 功能介绍 + +### Fast GPT V4.6 + +1. 新增 - 团队空间 diff --git a/docSite/content/docs/use-cases/kb.md b/docSite/content/docs/use-cases/kb.md index 335cfa9de..4ea1fd810 100644 --- a/docSite/content/docs/use-cases/kb.md +++ b/docSite/content/docs/use-cases/kb.md @@ -73,7 +73,7 @@ weight: 340 ![手动录入知识库结果](/imgs/9.png) 导入结果如上图。可以看到,我们均采用的是问答对的格式,而不是粗略的直接导入。目的就是为了模拟用户问题,进一步的提高向量搜索的匹配效果。可以为同一个问题设置多种问法,效果更佳。 -FastGPT 还提供了 openapi 功能,你可以在本地对特殊格式的文件进行处理后,再上传到 FastGPT,具体可以参考:[FastGPT Api Docs](https://doc.fastgpt.run/docs/development/openapi) +FastGPT 还提供了 openapi 功能,你可以在本地对特殊格式的文件进行处理后,再上传到 FastGPT,具体可以参考:[FastGPT Api Docs](https://doc.fastgpt.in/docs/development/openapi) ## 知识库微调和参数调整 diff --git a/package.json b/package.json index 93eb85359..7a82e8cb5 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,12 @@ }, "devDependencies": { "husky": "^8.0.3", - "lint-staged": "^13.2.1", - "prettier": "^3.0.3", "i18next": "^22.5.1", + "lint-staged": "^13.2.1", + "next-i18next": "^13.3.0", + "prettier": "^3.0.3", "react-i18next": "^12.3.1", - "next-i18next": "^13.3.0" + "zhlint": "^0.7.1" }, "lint-staged": { "./**/**/*.{ts,tsx,scss}": "npm run format-code", @@ -21,5 +22,8 @@ }, "engines": { "node": ">=18.0.0" + }, + "dependencies": { + "openai": "4.16.1" } } diff --git a/packages/global/common/bill/constants.ts b/packages/global/common/bill/constants.ts deleted file mode 100644 index e036a25a4..000000000 --- a/packages/global/common/bill/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const PRICE_SCALE = 100000; diff --git a/packages/global/common/bill/types/billReq.d.ts b/packages/global/common/bill/types/billReq.d.ts deleted file mode 100644 index 6957ee03b..000000000 --- a/packages/global/common/bill/types/billReq.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type CreateTrainingBillType = { - name: string; -}; diff --git a/packages/global/common/error/code/app.ts b/packages/global/common/error/code/app.ts new file mode 100644 index 000000000..70573f836 --- /dev/null +++ b/packages/global/common/error/code/app.ts @@ -0,0 +1,28 @@ +import { ErrType } from '../errorCode'; + +/* dataset: 502000 */ +export enum AppErrEnum { + unExist = 'unExist', + unAuthApp = 'unAuthApp' +} +const appErrList = [ + { + statusText: AppErrEnum.unExist, + message: '应用不存在' + }, + { + statusText: AppErrEnum.unAuthApp, + message: '无权操作该应用' + } +]; +export default appErrList.reduce((acc, cur, index) => { + return { + ...acc, + [cur.statusText]: { + code: 502000 + index, + statusText: cur.statusText, + message: cur.message, + data: null + } + }; +}, {} as ErrType<`${AppErrEnum}`>); diff --git a/packages/global/common/error/code/chat.ts b/packages/global/common/error/code/chat.ts new file mode 100644 index 000000000..00f5c8aed --- /dev/null +++ b/packages/global/common/error/code/chat.ts @@ -0,0 +1,23 @@ +import { ErrType } from '../errorCode'; + +/* dataset: 504000 */ +export enum ChatErrEnum { + unAuthChat = 'unAuthChat' +} +const errList = [ + { + statusText: ChatErrEnum.unAuthChat, + message: '无权操作该对话记录' + } +]; +export default errList.reduce((acc, cur, index) => { + return { + ...acc, + [cur.statusText]: { + code: 504000 + index, + statusText: cur.statusText, + message: cur.message, + data: null + } + }; +}, {} as ErrType<`${ChatErrEnum}`>); diff --git a/packages/global/common/error/code/dataset.ts b/packages/global/common/error/code/dataset.ts new file mode 100644 index 000000000..6efe6ece1 --- /dev/null +++ b/packages/global/common/error/code/dataset.ts @@ -0,0 +1,43 @@ +import { ErrType } from '../errorCode'; + +/* dataset: 501000 */ +export enum DatasetErrEnum { + unAuthDataset = 'unAuthDataset', + unCreateCollection = 'unCreateCollection', + unAuthDatasetCollection = 'unAuthDatasetCollection', + unAuthDatasetData = 'unAuthDatasetData', + unAuthDatasetFile = 'unAuthDatasetFile' +} +const datasetErr = [ + { + statusText: DatasetErrEnum.unAuthDataset, + message: '无权操作该知识库' + }, + { + statusText: DatasetErrEnum.unAuthDatasetCollection, + message: '无权操作该数据集' + }, + { + statusText: DatasetErrEnum.unAuthDatasetData, + message: '无权操作该数据' + }, + { + statusText: DatasetErrEnum.unAuthDatasetFile, + message: '无权操作该文件' + }, + { + statusText: DatasetErrEnum.unCreateCollection, + message: '无权创建数据集' + } +]; +export default datasetErr.reduce((acc, cur, index) => { + return { + ...acc, + [cur.statusText]: { + code: 501000 + index, + statusText: cur.statusText, + message: cur.message, + data: null + } + }; +}, {} as ErrType<`${DatasetErrEnum}`>); diff --git a/packages/global/common/error/code/openapi.ts b/packages/global/common/error/code/openapi.ts new file mode 100644 index 000000000..5737af63e --- /dev/null +++ b/packages/global/common/error/code/openapi.ts @@ -0,0 +1,28 @@ +import { ErrType } from '../errorCode'; + +/* dataset: 506000 */ +export enum OpenApiErrEnum { + unExist = 'unExist', + unAuth = 'unAuth' +} +const errList = [ + { + statusText: OpenApiErrEnum.unExist, + message: 'Api Key 不存在' + }, + { + statusText: OpenApiErrEnum.unAuth, + message: '无权操作该 Api Key' + } +]; +export default errList.reduce((acc, cur, index) => { + return { + ...acc, + [cur.statusText]: { + code: 506000 + index, + statusText: cur.statusText, + message: cur.message, + data: null + } + }; +}, {} as ErrType<`${OpenApiErrEnum}`>); diff --git a/packages/global/common/error/code/outLink.ts b/packages/global/common/error/code/outLink.ts new file mode 100644 index 000000000..ce726add1 --- /dev/null +++ b/packages/global/common/error/code/outLink.ts @@ -0,0 +1,34 @@ +import { ErrType } from '../errorCode'; + +/* dataset: 505000 */ +export enum OutLinkErrEnum { + unExist = 'unExist', + unAuthLink = 'unAuthLink', + linkUnInvalid = 'linkUnInvalid' +} +const errList = [ + { + statusText: OutLinkErrEnum.unExist, + message: '分享链接不存在' + }, + { + statusText: OutLinkErrEnum.unAuthLink, + message: '分享链接无效' + }, + { + code: 501, + statusText: OutLinkErrEnum.linkUnInvalid, + message: '分享链接无效' + } +]; +export default errList.reduce((acc, cur, index) => { + return { + ...acc, + [cur.statusText]: { + code: cur?.code || 505000 + index, + statusText: cur.statusText, + message: cur.message, + data: null + } + }; +}, {} as ErrType<`${OutLinkErrEnum}`>); diff --git a/packages/global/common/error/code/plugin.ts b/packages/global/common/error/code/plugin.ts new file mode 100644 index 000000000..d3ae672a8 --- /dev/null +++ b/packages/global/common/error/code/plugin.ts @@ -0,0 +1,28 @@ +import { ErrType } from '../errorCode'; + +/* dataset: 507000 */ +export enum PluginErrEnum { + unExist = 'unExist', + unAuth = 'unAuth' +} +const errList = [ + { + statusText: PluginErrEnum.unExist, + message: '插件不存在' + }, + { + statusText: PluginErrEnum.unAuth, + message: '无权操作该插件' + } +]; +export default errList.reduce((acc, cur, index) => { + return { + ...acc, + [cur.statusText]: { + code: 507000 + index, + statusText: cur.statusText, + message: cur.message, + data: null + } + }; +}, {} as ErrType<`${PluginErrEnum}`>); diff --git a/packages/global/common/error/code/team.ts b/packages/global/common/error/code/team.ts new file mode 100644 index 000000000..315c9abf1 --- /dev/null +++ b/packages/global/common/error/code/team.ts @@ -0,0 +1,22 @@ +import { ErrType } from '../errorCode'; + +/* team: 500000 */ +export enum TeamErrEnum { + teamOverSize = 'teamOverSize', + unAuthTeam = 'unAuthTeam' +} +const teamErr = [ + { statusText: TeamErrEnum.teamOverSize, message: 'error.team.overSize' }, + { statusText: TeamErrEnum.unAuthTeam, message: '无权操作该团队' } +]; +export default teamErr.reduce((acc, cur, index) => { + return { + ...acc, + [cur.statusText]: { + code: 500000 + index, + statusText: cur.statusText, + message: cur.message, + data: null + } + }; +}, {} as ErrType<`${TeamErrEnum}`>); diff --git a/packages/global/common/error/code/user.ts b/packages/global/common/error/code/user.ts new file mode 100644 index 000000000..028b65273 --- /dev/null +++ b/packages/global/common/error/code/user.ts @@ -0,0 +1,26 @@ +import { ErrType } from '../errorCode'; + +/* team: 503000 */ +export enum UserErrEnum { + unAuthUser = 'unAuthUser', + unAuthRole = 'unAuthRole', + binVisitor = 'binVisitor', + balanceNotEnough = 'balanceNotEnough' +} +const errList = [ + { statusText: UserErrEnum.unAuthUser, message: '找不到该用户' }, + { statusText: UserErrEnum.binVisitor, message: '您的身份校验未通过' }, + { statusText: UserErrEnum.binVisitor, message: '您当前身份为游客,无权操作' }, + { statusText: UserErrEnum.balanceNotEnough, message: '账号余额不足~' } +]; +export default errList.reduce((acc, cur, index) => { + return { + ...acc, + [cur.statusText]: { + code: 503000 + index, + statusText: cur.statusText, + message: cur.message, + data: null + } + }; +}, {} as ErrType<`${UserErrEnum}`>); diff --git a/packages/global/common/error/errorCode.ts b/packages/global/common/error/errorCode.ts index 94f81125c..380c3b33b 100644 --- a/packages/global/common/error/errorCode.ts +++ b/packages/global/common/error/errorCode.ts @@ -1,3 +1,12 @@ +import appErr from './code/app'; +import chatErr from './code/chat'; +import datasetErr from './code/dataset'; +import openapiErr from './code/openapi'; +import pluginErr from './code/plugin'; +import outLinkErr from './code/outLink'; +import teamErr from './code/team'; +import userErr from './code/user'; + export const ERROR_CODE: { [key: number]: string } = { 400: '请求失败', 401: '无权访问', @@ -27,10 +36,19 @@ export enum ERROR_ENUM { insufficientQuota = 'insufficientQuota', unAuthModel = 'unAuthModel', unAuthApiKey = 'unAuthApiKey', - unAuthDataset = 'unAuthDataset', - unAuthDatasetCollection = 'unAuthDatasetCollection', unAuthFile = 'unAuthFile' } + +export type ErrType = Record< + string, + { + code: number; + statusText: T; + message: string; + data: null; + } +>; + export const ERROR_RESPONSE: Record< any, { @@ -55,15 +73,10 @@ export const ERROR_RESPONSE: Record< [ERROR_ENUM.unAuthModel]: { code: 511, statusText: ERROR_ENUM.unAuthModel, - message: '无权使用该模型', - data: null - }, - [ERROR_ENUM.unAuthDataset]: { - code: 512, - statusText: ERROR_ENUM.unAuthDataset, - message: '无权使用该知识库', + message: '无权操作该模型', data: null }, + [ERROR_ENUM.unAuthFile]: { code: 513, statusText: ERROR_ENUM.unAuthFile, @@ -76,10 +89,12 @@ export const ERROR_RESPONSE: Record< message: 'Api Key 不合法', data: null }, - [ERROR_ENUM.unAuthDatasetCollection]: { - code: 515, - statusText: ERROR_ENUM.unAuthDatasetCollection, - message: '无权使用该知识库文件', - data: null - } + ...appErr, + ...chatErr, + ...datasetErr, + ...openapiErr, + ...outLinkErr, + ...teamErr, + ...userErr, + ...pluginErr }; diff --git a/packages/global/common/file/constants.ts b/packages/global/common/file/constants.ts new file mode 100644 index 000000000..a5e42b2f4 --- /dev/null +++ b/packages/global/common/file/constants.ts @@ -0,0 +1,5 @@ +export enum BucketNameEnum { + dataset = 'dataset' +} + +export const FileBaseUrl = '/api/common/file/read'; diff --git a/packages/global/common/file/type.d.ts b/packages/global/common/file/type.d.ts new file mode 100644 index 000000000..dfe8b21c6 --- /dev/null +++ b/packages/global/common/file/type.d.ts @@ -0,0 +1,8 @@ +import { BucketNameEnum } from './constants'; + +export type FileTokenQuery = { + bucketName: `${BucketNameEnum}`; + teamId: string; + tmbId: string; + fileId: string; +}; diff --git a/packages/global/core/ai/constant.ts b/packages/global/core/ai/constant.ts index 4773d0895..9e44d3ccc 100644 --- a/packages/global/core/ai/constant.ts +++ b/packages/global/core/ai/constant.ts @@ -2,5 +2,6 @@ export enum ChatCompletionRequestMessageRoleEnum { 'System' = 'system', 'User' = 'user', 'Assistant' = 'assistant', - 'Function' = 'function' + 'Function' = 'function', + 'Tool' = 'tool' } diff --git a/packages/global/core/ai/index.ts b/packages/global/core/ai/index.ts new file mode 100644 index 000000000..208942d5f --- /dev/null +++ b/packages/global/core/ai/index.ts @@ -0,0 +1,2 @@ +import OpenAI from 'openai'; +export default OpenAI; diff --git a/projects/app/src/types/model.d.ts b/packages/global/core/ai/model.d.ts similarity index 78% rename from projects/app/src/types/model.d.ts rename to packages/global/core/ai/model.d.ts index 1a33563ca..3e187510c 100644 --- a/projects/app/src/types/model.d.ts +++ b/packages/global/core/ai/model.d.ts @@ -1,9 +1,8 @@ -import { LLMModelUsageEnum } from '@/constants/model'; - export type LLMModelItemType = { model: string; name: string; - maxToken: number; + maxContext: number; + maxResponse: number; price: number; }; export type ChatModelItemType = LLMModelItemType & { @@ -25,3 +24,9 @@ export type VectorModelItemType = { price: number; maxToken: number; }; + +export type AudioSpeechModelType = { + model: string; + name: string; + price: number; +}; diff --git a/projects/app/src/constants/model.ts b/packages/global/core/ai/model.ts similarity index 59% rename from projects/app/src/constants/model.ts rename to packages/global/core/ai/model.ts index 673e06f66..b39dbf348 100644 --- a/projects/app/src/constants/model.ts +++ b/packages/global/core/ai/model.ts @@ -1,18 +1,18 @@ -import type { AppSchema } from '@/types/mongoSchema'; -import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; import type { LLMModelItemType, ChatModelItemType, FunctionModelItemType, - VectorModelItemType -} from '@/types/model'; + VectorModelItemType, + AudioSpeechModelType +} from './model.d'; export const defaultChatModels: ChatModelItemType[] = [ { - model: 'gpt-3.5-turbo', - name: 'GPT35-4k', + model: 'gpt-3.5-turbo-1106', + name: 'GPT35-1106', price: 0, - maxToken: 4000, + maxContext: 16000, + maxResponse: 4000, quoteMaxToken: 2000, maxTemperature: 1.2, censor: false, @@ -21,7 +21,8 @@ export const defaultChatModels: ChatModelItemType[] = [ { model: 'gpt-3.5-turbo-16k', name: 'GPT35-16k', - maxToken: 16000, + maxContext: 16000, + maxResponse: 16000, price: 0, quoteMaxToken: 8000, maxTemperature: 1.2, @@ -31,7 +32,8 @@ export const defaultChatModels: ChatModelItemType[] = [ { model: 'gpt-4', name: 'GPT4-8k', - maxToken: 8000, + maxContext: 8000, + maxResponse: 8000, price: 0, quoteMaxToken: 4000, maxTemperature: 1.2, @@ -43,15 +45,17 @@ export const defaultQAModels: LLMModelItemType[] = [ { model: 'gpt-3.5-turbo-16k', name: 'GPT35-16k', - maxToken: 16000, + maxContext: 16000, + maxResponse: 16000, price: 0 } ]; export const defaultCQModels: FunctionModelItemType[] = [ { - model: 'gpt-3.5-turbo-16k', - name: 'GPT35-16k', - maxToken: 16000, + model: 'gpt-3.5-turbo-1106', + name: 'GPT35-1106', + maxContext: 16000, + maxResponse: 4000, price: 0, functionCall: true, functionPrompt: '' @@ -59,7 +63,8 @@ export const defaultCQModels: FunctionModelItemType[] = [ { model: 'gpt-4', name: 'GPT4-8k', - maxToken: 8000, + maxContext: 8000, + maxResponse: 8000, price: 0, functionCall: true, functionPrompt: '' @@ -67,9 +72,10 @@ export const defaultCQModels: FunctionModelItemType[] = [ ]; export const defaultExtractModels: FunctionModelItemType[] = [ { - model: 'gpt-3.5-turbo-16k', - name: 'GPT35-16k', - maxToken: 16000, + model: 'gpt-3.5-turbo-1106', + name: 'GPT35-1106', + maxContext: 16000, + maxResponse: 4000, price: 0, functionCall: true, functionPrompt: '' @@ -77,9 +83,10 @@ export const defaultExtractModels: FunctionModelItemType[] = [ ]; export const defaultQGModels: LLMModelItemType[] = [ { - model: 'gpt-3.5-turbo', - name: 'GPT35-4K', - maxToken: 4000, + model: 'gpt-3.5-turbo-1106', + name: 'GPT35-1106', + maxContext: 1600, + maxResponse: 4000, price: 0 } ]; @@ -94,27 +101,15 @@ export const defaultVectorModels: VectorModelItemType[] = [ } ]; -export const defaultApp: AppSchema = { - _id: '', - userId: 'userId', - name: '模型加载中', - type: 'basic', - avatar: '/icon/logo.svg', - intro: '', - updateTime: Date.now(), - share: { - isShare: false, - isShareDetail: false, - collection: 0 +export const defaultAudioSpeechModels: AudioSpeechModelType[] = [ + { + model: 'tts-1', + name: 'OpenAI TTS1', + price: 0 }, - modules: [] -}; - -export const defaultOutLinkForm: OutLinkEditType = { - name: '', - responseDetail: false, - limit: { - QPM: 100, - credit: -1 + { + model: 'tts-1-hd', + name: 'OpenAI TTS1', + price: 0 } -}; +]; diff --git a/packages/global/core/ai/speech/api.d.ts b/packages/global/core/ai/speech/api.d.ts new file mode 100644 index 000000000..b3298f876 --- /dev/null +++ b/packages/global/core/ai/speech/api.d.ts @@ -0,0 +1,8 @@ +import { Text2SpeechVoiceEnum } from './constant'; + +export type Text2SpeechProps = { + model?: string; + voice?: `${Text2SpeechVoiceEnum}`; + input: string; + speed?: number; +}; diff --git a/packages/global/core/ai/speech/constant.ts b/packages/global/core/ai/speech/constant.ts new file mode 100644 index 000000000..47636c252 --- /dev/null +++ b/packages/global/core/ai/speech/constant.ts @@ -0,0 +1,17 @@ +export enum Text2SpeechVoiceEnum { + alloy = 'alloy', + echo = 'echo', + fable = 'fable', + onyx = 'onyx', + nova = 'nova', + shimmer = 'shimmer' +} +export const openaiTTSList = [ + Text2SpeechVoiceEnum.alloy, + Text2SpeechVoiceEnum.echo, + Text2SpeechVoiceEnum.fable, + Text2SpeechVoiceEnum.onyx, + Text2SpeechVoiceEnum.nova, + Text2SpeechVoiceEnum.shimmer +]; +export const openaiTTSModel = 'tts-1'; diff --git a/packages/global/core/ai/type.d.ts b/packages/global/core/ai/type.d.ts index 723af8e6a..1689ee044 100644 --- a/packages/global/core/ai/type.d.ts +++ b/packages/global/core/ai/type.d.ts @@ -1,9 +1,19 @@ -import OpenAI from 'openai'; -export type ChatCompletionRequestMessage = OpenAI.Chat.CreateChatCompletionRequestMessage; -export type ChatCompletion = OpenAI.Chat.ChatCompletion; -export type CreateChatCompletionRequest = OpenAI.Chat.ChatCompletionCreateParams; +import type { + ChatCompletion, + ChatCompletionCreateParams, + ChatCompletionChunk, + ChatCompletionMessageParam, + ChatCompletionContentPart +} from 'openai/resources'; +export type ChatCompletionContentPart = ChatCompletionContentPart; +export type ChatCompletionCreateParams = ChatCompletionCreateParams; +export type ChatMessageItemType = Omit & { + dataId?: string; + content: any; +}; -export type StreamChatType = Stream; +export type ChatCompletion = ChatCompletion; +export type StreamChatType = Stream; export type PromptTemplateItem = { title: string; diff --git a/packages/global/core/app/api.d.ts b/packages/global/core/app/api.d.ts new file mode 100644 index 000000000..2fad2771c --- /dev/null +++ b/packages/global/core/app/api.d.ts @@ -0,0 +1,18 @@ +import { AppTypeEnum } from './constants'; +import { AppSchema } from './type'; + +export type CreateAppParams = { + name?: string; + avatar?: string; + type?: `${AppTypeEnum}`; + modules: AppSchema['modules']; +}; + +export interface AppUpdateParams { + name?: string; + type?: `${AppTypeEnum}`; + avatar?: string; + intro?: string; + modules?: AppSchema['modules']; + permission?: AppSchema['permission']; +} diff --git a/packages/global/core/app/constants.ts b/packages/global/core/app/constants.ts new file mode 100644 index 000000000..066d31312 --- /dev/null +++ b/packages/global/core/app/constants.ts @@ -0,0 +1,4 @@ +export enum AppTypeEnum { + basic = 'basic', + advanced = 'advanced' +} diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts new file mode 100644 index 000000000..2e01e7cb3 --- /dev/null +++ b/packages/global/core/app/type.d.ts @@ -0,0 +1,32 @@ +import { ModuleItemType } from '../module/type'; +import { AppTypeEnum } from './constants'; +import { PermissionTypeEnum } from '../../support/permission/constant'; +import { Text2SpeechVoiceEnum } from '../ai/speech/constant'; + +export interface AppSchema { + _id: string; + userId: string; + teamId: string; + tmbId: string; + name: string; + type: `${AppTypeEnum}`; + avatar: string; + intro: string; + updateTime: number; + modules: ModuleItemType[]; + permission: `${PermissionTypeEnum}`; +} + +export type AppListItemType = { + _id: string; + name: string; + avatar: string; + intro: string; + isOwner: boolean; + permission: `${PermissionTypeEnum}`; +}; + +export type AppDetailType = AppSchema & { + isOwner: boolean; + canWrite: boolean; +}; diff --git a/packages/global/core/chat/api.d.ts b/packages/global/core/chat/api.d.ts new file mode 100644 index 000000000..97844d5f6 --- /dev/null +++ b/packages/global/core/chat/api.d.ts @@ -0,0 +1,34 @@ +import { ModuleItemType } from '../module/type'; +import { AdminFbkType, ChatItemType, moduleDispatchResType } from './type'; + +export type UpdateHistoryProps = { + chatId: string; + customTitle?: string; + top?: boolean; +}; + +export type AdminUpdateFeedbackParams = AdminFbkType & { + chatItemId: string; +}; + +export type InitChatResponse = { + chatId: string; + appId: string; + app: { + userGuideModule?: ModuleItemType; + chatModels?: string[]; + name: string; + avatar: string; + intro: string; + canUse?: boolean; + }; + title: string; + variables: Record; + history: ChatItemType[]; +}; + +export type ChatHistoryItemResType = moduleDispatchResType & { + moduleType: `${FlowNodeTypeEnum}`; + moduleName: string; + moduleLogo?: string; +}; diff --git a/projects/app/src/constants/chat.ts b/packages/global/core/chat/constants.ts similarity index 80% rename from projects/app/src/constants/chat.ts rename to packages/global/core/chat/constants.ts index 550f26690..2313fb445 100644 --- a/projects/app/src/constants/chat.ts +++ b/packages/global/core/chat/constants.ts @@ -1,16 +1,9 @@ -import dayjs from 'dayjs'; - -export enum sseResponseEventEnum { - error = 'error', - answer = 'answer', - moduleStatus = 'moduleStatus', - appStreamResponse = 'appStreamResponse' // sse response request -} - export enum ChatRoleEnum { System = 'System', Human = 'Human', - AI = 'AI' + AI = 'AI', + Function = 'Function', + Tool = 'Tool' } export enum TaskResponseKeyEnum { @@ -28,6 +21,12 @@ export const ChatRoleMap = { }, [ChatRoleEnum.AI]: { name: 'AI' + }, + [ChatRoleEnum.Function]: { + name: 'Function' + }, + [ChatRoleEnum.Tool]: { + name: 'Tool' } }; diff --git a/packages/global/core/chat/type.d.ts b/packages/global/core/chat/type.d.ts new file mode 100644 index 000000000..4212decb8 --- /dev/null +++ b/packages/global/core/chat/type.d.ts @@ -0,0 +1,111 @@ +import { ClassifyQuestionAgentItemType } from '../module/type'; +import { SearchDataResponseItemType } from '../dataset/type'; +import { ChatRoleEnum, ChatSourceEnum, TaskResponseKeyEnum } from './constants'; +import { FlowNodeTypeEnum } from '../module/node/constant'; +import { AppSchema } from 'core/app/type'; + +export type ChatSchema = { + _id: string; + chatId: string; + userId: string; + teamId: string; + tmbId: string; + appId: string; + updateTime: Date; + title: string; + customTitle: string; + top: boolean; + variables: Record; + source: `${ChatSourceEnum}`; + shareId?: string; + isInit: boolean; + content: ChatItemType[]; +}; + +export type ChatWithAppSchema = Omit & { + appId: AppSchema; +}; + +export type ChatItemSchema = { + dataId: string; + chatId: string; + userId: string; + teamId: string; + tmbId: string; + appId: string; + time: Date; + obj: `${ChatRoleEnum}`; + value: string; + userFeedback?: string; + adminFeedback?: AdminFbkType; + [TaskResponseKeyEnum.responseData]?: ChatHistoryItemResType[]; + tts?: Buffer; +}; + +export type AdminFbkType = { + dataId: string; + datasetId: string; + collectionId: string; + q: string; + a?: string; +}; + +export type ChatItemType = { + dataId?: string; + obj: ChatItemSchema['obj']; + value: any; + userFeedback?: string; + adminFeedback?: ChatItemSchema['feedback']; + [TaskResponseKeyEnum.responseData]?: ChatItemSchema[TaskResponseKeyEnum.responseData]; +}; + +export type ChatSiteItemType = { + status: 'loading' | 'running' | 'finish'; + moduleName?: string; + ttsBuffer?: Buffer; +} & ChatItemType; + +export type HistoryItemType = { + chatId: string; + updateTime: Date; + customTitle?: string; + title: string; +}; +export type ChatHistoryItemType = HistoryItemType & { + appId: string; + top: boolean; +}; + +// response data +export type moduleDispatchResType = { + price: number; + runningTime?: number; + tokens?: number; + model?: string; + + // chat + question?: string; + temperature?: number; + maxToken?: number; + quoteList?: SearchDataResponseItemType[]; + historyPreview?: ChatItemType[]; // completion context array. history will slice + + // dataset search + similarity?: number; + limit?: number; + + // cq + cqList?: ClassifyQuestionAgentItemType[]; + cqResult?: string; + + // content extract + extractDescription?: string; + extractResult?: Record; + + // http + body?: Record; + httpResult?: Record; + + // plugin output + pluginOutput?: Record; +}; diff --git a/packages/global/core/dataset/constant.ts b/packages/global/core/dataset/constant.ts index 6c01f56b8..822ffc54d 100644 --- a/packages/global/core/dataset/constant.ts +++ b/packages/global/core/dataset/constant.ts @@ -1,3 +1,5 @@ +export const PgDatasetTableName = 'modeldata'; + export enum DatasetTypeEnum { folder = 'folder', dataset = 'dataset' diff --git a/packages/global/core/dataset/type.d.ts b/packages/global/core/dataset/type.d.ts index 13e4c70b8..3f4173d69 100644 --- a/packages/global/core/dataset/type.d.ts +++ b/packages/global/core/dataset/type.d.ts @@ -1,20 +1,26 @@ +import { PermissionTypeEnum } from '../../support/permission/constant'; import { DatasetCollectionTypeEnum, DatasetTypeEnum, TrainingModeEnum } from './constant'; export type DatasetSchemaType = { _id: string; - userId: string; parentId: string; + userId: string; + teamId: string; + tmbId: string; updateTime: Date; avatar: string; name: string; vectorModel: string; tags: string[]; type: `${DatasetTypeEnum}`; + permission: `${PermissionTypeEnum}`; }; export type DatasetCollectionSchemaType = { _id: string; userId: string; + teamId: string; + tmbId: string; datasetId: string; parentId?: string; name: string; @@ -30,6 +36,8 @@ export type DatasetCollectionSchemaType = { export type DatasetTrainingSchemaType = { _id: string; userId: string; + teamId: string; + tmbId: string; datasetId: string; datasetCollectionId: string; billId: string; @@ -42,17 +50,35 @@ export type DatasetTrainingSchemaType = { a: string; }; +export type CollectionWithDatasetType = Omit & { + datasetId: DatasetSchemaType; +}; + /* ================= dataset ===================== */ /* ================= collection ===================== */ +export type DatasetCollectionItemType = DatasetCollectionSchemaType & { + canWrite: boolean; +}; /* ================= data ===================== */ +export type PgRawDataItemType = { + id: string; + q: string; + a: string; + team_id: string; + tmb_id: string; + dataset_id: string; + collection_id: string; +}; export type PgDataItemType = { id: string; q: string; a: string; - dataset_id: string; - collection_id: string; + teamId: string; + tmbId: string; + datasetId: string; + collectionId: string; }; export type DatasetChunkItemType = { q: string; @@ -66,8 +92,24 @@ export type DatasetDataItemType = DatasetChunkItemType & { sourceId?: string; }; +/* --------------- file ---------------------- */ +export type DatasetFileSchema = { + _id: string; + length: number; + chunkSize: number; + uploadDate: Date; + filename: string; + contentType: string; + metadata: { + contentType: string; + datasetId: string; + teamId: string; + tmbId: string; + }; +}; + /* ============= search =============== */ -export type SearchDataResultItemType = PgDataItemType & { +export type SearchDataResultItemType = PgRawDataItemType & { score: number; }; export type SearchDataResponseItemType = DatasetDataItemType & { diff --git a/packages/global/core/module/api.d.ts b/packages/global/core/module/api.d.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/global/core/module/type.d.ts b/packages/global/core/module/type.d.ts index c3d1f81fd..f4c339618 100644 --- a/packages/global/core/module/type.d.ts +++ b/packages/global/core/module/type.d.ts @@ -1,4 +1,4 @@ -import { FlowNodeTypeEnum } from './node/constant'; +import { FlowNodeTypeEnum, FlowNodeValTypeEnum } from './node/constant'; import { FlowNodeInputItemType, FlowNodeOutputItemType } from './node/type'; export type FlowModuleTemplateType = { @@ -42,3 +42,14 @@ export type SelectAppItemType = { name: string; logo: string; }; + +/* agent */ +export type ClassifyQuestionAgentItemType = { + value: string; + key: string; +}; +export type ContextExtractAgentItemType = { + desc: string; + key: string; + required: boolean; +}; diff --git a/packages/global/core/plugin/type.d.ts b/packages/global/core/plugin/type.d.ts index 8a9e24e23..ebb3857ab 100644 --- a/packages/global/core/plugin/type.d.ts +++ b/packages/global/core/plugin/type.d.ts @@ -3,6 +3,8 @@ import type { ModuleItemType } from '../module/type.d'; export type PluginItemSchema = { _id: string; userId: string; + teamId: string; + tmbId: string; name: string; avatar: string; intro: string; diff --git a/packages/global/package.json b/packages/global/package.json index 7b60a291f..17ff0598c 100644 --- a/packages/global/package.json +++ b/packages/global/package.json @@ -6,7 +6,7 @@ "timezones-list": "^3.0.2", "dayjs": "^1.11.7", "encoding": "^0.1.13", - "openai": "^4.12.1" + "openai": "^4.16.1" }, "devDependencies": { "@types/node": "^20.8.5" diff --git a/packages/global/support/openapi/type.d.ts b/packages/global/support/openapi/type.d.ts index f042cb5cf..66dd1426f 100644 --- a/packages/global/support/openapi/type.d.ts +++ b/packages/global/support/openapi/type.d.ts @@ -1,6 +1,8 @@ export type OpenApiSchema = { _id: string; userId: string; + teamId: string; + tmbId: string; createTime: Date; lastUsedTime?: Date; apiKey: string; diff --git a/packages/global/support/outLink/api.d.ts b/packages/global/support/outLink/api.d.ts new file mode 100644 index 000000000..6523e2b5b --- /dev/null +++ b/packages/global/support/outLink/api.d.ts @@ -0,0 +1,27 @@ +import type { HistoryItemType, ChatSiteItemType } from '../../core/chat/type.d'; +import type { InitChatResponse } from '../../core/chat/api.d'; +import { OutLinkSchema } from '@fastgpt/global/support/outLink/type'; + +export type InitShareChatResponse = { + userAvatar: string; + app: InitChatResponse['app']; +}; + +/* one page type */ +export type ShareChatType = InitShareChatResponse & { + history: ShareChatHistoryItemType; +}; + +/* history list item type */ +export type ShareChatHistoryItemType = HistoryItemType & { + shareId: string; + variables?: Record; + chats: ChatSiteItemType[]; +}; + +export type AuthLinkChatProps = { ip?: string | null; authToken?: string; question: string }; +export type AuthLinkLimitProps = AuthLinkChatProps & { outLink: OutLinkSchema }; +export type AuthShareChatInitProps = { + authToken?: string; + tokenUrl?: string; +}; diff --git a/packages/global/support/outLink/type.d.ts b/packages/global/support/outLink/type.d.ts index 58fa8b207..02e09e164 100644 --- a/packages/global/support/outLink/type.d.ts +++ b/packages/global/support/outLink/type.d.ts @@ -4,6 +4,8 @@ export type OutLinkSchema = { _id: string; shareId: string; userId: string; + teamId: string; + tmbId: string; appId: string; name: string; total: number; diff --git a/packages/global/support/permission/constant.ts b/packages/global/support/permission/constant.ts new file mode 100644 index 000000000..c0624a1e9 --- /dev/null +++ b/packages/global/support/permission/constant.ts @@ -0,0 +1,21 @@ +export enum AuthUserTypeEnum { + token = 'token', + root = 'root', + apikey = 'apikey', + outLink = 'outLink' +} + +export enum PermissionTypeEnum { + 'private' = 'private', + 'public' = 'public' +} +export const PermissionTypeMap = { + [PermissionTypeEnum.private]: { + iconLight: 'support/permission/privateLight', + label: 'permission.Private' + }, + [PermissionTypeEnum.public]: { + iconLight: 'support/permission/publicLight', + label: 'permission.Public' + } +}; diff --git a/packages/global/support/permission/type.d.ts b/packages/global/support/permission/type.d.ts new file mode 100644 index 000000000..eac0ce9ff --- /dev/null +++ b/packages/global/support/permission/type.d.ts @@ -0,0 +1,12 @@ +import { AuthUserTypeEnum } from './constant'; + +export type AuthResponseType = { + userId: string; + teamId: string; + tmbId: string; + isOwner: boolean; + canWrite: boolean; + authType?: `${AuthUserTypeEnum}`; + appId?: string; + apikey?: string; +}; diff --git a/packages/global/support/permission/utils.ts b/packages/global/support/permission/utils.ts new file mode 100644 index 000000000..4e0df70fe --- /dev/null +++ b/packages/global/support/permission/utils.ts @@ -0,0 +1,27 @@ +import { TeamMemberRoleEnum } from '../user/team/constant'; +import { PermissionTypeEnum } from './constant'; + +/* team public source, or owner source in team */ +export function mongoRPermission({ + teamId, + tmbId, + role +}: { + teamId: string; + tmbId: string; + role: `${TeamMemberRoleEnum}`; +}) { + return { + teamId, + ...(role === TeamMemberRoleEnum.visitor && { permission: PermissionTypeEnum.public }), + ...(role === TeamMemberRoleEnum.admin && { + $or: [{ permission: PermissionTypeEnum.public }, { tmbId }] + }) + }; +} +export function mongoOwnerPermission({ teamId, tmbId }: { teamId: string; tmbId: string }) { + return { + teamId, + tmbId + }; +} diff --git a/packages/global/support/user/api.d.ts b/packages/global/support/user/api.d.ts new file mode 100644 index 000000000..1419fedf4 --- /dev/null +++ b/packages/global/support/user/api.d.ts @@ -0,0 +1,15 @@ +import { OAuthEnum } from './constant'; + +export type PostLoginProps = { + username: string; + password: string; + tmbId?: string; +}; + +export type OauthLoginProps = { + type: `${OAuthEnum}`; + code: string; + callbackUrl: string; + inviterId?: string; + tmbId?: string; +}; diff --git a/packages/global/support/user/constant.ts b/packages/global/support/user/constant.ts index 6d91e5960..159341901 100644 --- a/packages/global/support/user/constant.ts +++ b/packages/global/support/user/constant.ts @@ -1,30 +1,4 @@ -export enum InformTypeEnum { - system = 'system' +export enum OAuthEnum { + github = 'github', + google = 'google' } - -export const InformTypeMap = { - [InformTypeEnum.system]: { - label: '系统通知' - } -}; - -export enum TeamMemberRoleEnum { - owner = 'owner', - admin = 'admin', - member = 'member', - visitor = 'visitor' -} -export const TeamMemberRoleMap = { - [TeamMemberRoleEnum.owner]: { - label: 'user.team.role.owner' - }, - [TeamMemberRoleEnum.admin]: { - label: 'user.team.role.admin' - }, - [TeamMemberRoleEnum.member]: { - label: 'user.team.role.member' - }, - [TeamMemberRoleEnum.visitor]: { - label: 'user.team.role.visitor' - } -}; diff --git a/packages/global/support/user/controller.d.ts b/packages/global/support/user/controller.d.ts deleted file mode 100644 index 0979da826..000000000 --- a/packages/global/support/user/controller.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -export type CreateTeamProps = { - ownerId: string; - name: string; - avatar?: string; -}; -export type UpdateTeamProps = { - id: string; - name?: string; - avatar?: string; -}; -export type updateTeamBalanceProps = { - id: string; - balance: number; -}; - -export type CreateTeamMemberProps = { - ownerId: string; - teamId: string; - userId: string; - name?: string; -}; diff --git a/packages/global/support/user/inform/constants.ts b/packages/global/support/user/inform/constants.ts new file mode 100644 index 000000000..6c9adbae7 --- /dev/null +++ b/packages/global/support/user/inform/constants.ts @@ -0,0 +1,9 @@ +export enum InformTypeEnum { + system = 'system' +} + +export const InformTypeMap = { + [InformTypeEnum.system]: { + label: '系统通知' + } +}; diff --git a/packages/global/support/user/inform/type.d.ts b/packages/global/support/user/inform/type.d.ts new file mode 100644 index 000000000..5291e936f --- /dev/null +++ b/packages/global/support/user/inform/type.d.ts @@ -0,0 +1,18 @@ +import { InformTypeEnum } from './constant'; + +export type SendInformProps = { + tmbId?: string; + type: `${InformTypeEnum}`; + title: string; + content: string; +}; + +export type UserInformSchema = { + _id: string; + userId: string; + time: Date; + type: `${InformTypeEnum}`; + title: string; + content: string; + read: boolean; +}; diff --git a/packages/global/support/user/team/constant.ts b/packages/global/support/user/team/constant.ts new file mode 100644 index 000000000..5768020b1 --- /dev/null +++ b/packages/global/support/user/team/constant.ts @@ -0,0 +1,42 @@ +export const TeamCollectionName = 'teams'; +export const TeamMemberCollectionName = 'team.members'; + +export enum TeamMemberRoleEnum { + owner = 'owner', + admin = 'admin', + visitor = 'visitor' +} +export const TeamMemberRoleMap = { + [TeamMemberRoleEnum.owner]: { + value: TeamMemberRoleEnum.owner, + label: 'user.team.role.Owner' + }, + [TeamMemberRoleEnum.admin]: { + value: TeamMemberRoleEnum.admin, + label: 'user.team.role.Admin' + }, + [TeamMemberRoleEnum.visitor]: { + value: TeamMemberRoleEnum.visitor, + label: 'user.team.role.Visitor' + } +}; + +export enum TeamMemberStatusEnum { + waiting = 'waiting', + active = 'active', + reject = 'reject' +} +export const TeamMemberStatusMap = { + [TeamMemberStatusEnum.waiting]: { + label: 'user.team.member.waiting', + color: 'orange.600' + }, + [TeamMemberStatusEnum.active]: { + label: 'user.team.member.active', + color: 'green.600' + }, + [TeamMemberStatusEnum.reject]: { + label: 'user.team.member.reject', + color: 'red.600' + } +}; diff --git a/packages/global/support/user/team/controller.d.ts b/packages/global/support/user/team/controller.d.ts new file mode 100644 index 000000000..cf1f668be --- /dev/null +++ b/packages/global/support/user/team/controller.d.ts @@ -0,0 +1,40 @@ +import { TeamMemberRoleEnum } from './constant'; +import { TeamMemberSchema } from './type'; + +export type AuthTeamRoleProps = { + teamId: string; + tmbId: string; + role?: `${TeamMemberRoleEnum}`; +}; +export type CreateTeamProps = { + name: string; + avatar?: string; + defaultTeam?: boolean; +}; +export type UpdateTeamProps = { + teamId: string; + name?: string; + avatar?: string; +}; + +/* ------------- member ----------- */ +export type DelMemberProps = { + teamId: string; + memberId: string; +}; +export type UpdateTeamMemberProps = { + teamId: string; + memberId: string; + role?: TeamMemberSchema['role']; + status?: TeamMemberSchema['status']; +}; +export type InviteMemberProps = { + teamId: string; + usernames: string[]; + role: `${TeamMemberRoleEnum}`; +}; +export type UpdateInviteProps = { + tmbId: string; + status: TeamMemberSchema['status']; +}; +export type InviteMemberResponse = Record<'invite' | 'inValid' | 'inTeam', string[]>; diff --git a/packages/global/support/user/team/type.d.ts b/packages/global/support/user/team/type.d.ts new file mode 100644 index 000000000..5373e7a42 --- /dev/null +++ b/packages/global/support/user/team/type.d.ts @@ -0,0 +1,46 @@ +import { UserModelSchema } from '../type'; +import { TeamMemberRoleEnum, TeamMemberStatusEnum } from './constant'; + +export type TeamSchema = { + _id: string; + name: string; + ownerId: string; + avatar: string; + createTime: Date; + balance: number; + maxSize: number; +}; + +export type TeamMemberSchema = { + _id: string; + teamId: string; + userId: string; + createTime: Date; + role: `${TeamMemberRoleEnum}`; + status: `${TeamMemberStatusEnum}`; + defaultTeam: boolean; +}; + +export type TeamItemType = { + userId: string; + teamId: string; + teamName: string; + avatar: string; + balance: number; + tmbId: string; + defaultTeam: boolean; + role: `${TeamMemberRoleEnum}`; + status: `${TeamMemberStatusEnum}`; + canWrite: boolean; + maxSize: number; +}; + +export type TeamMemberItemType = { + userId: string; + tmbId: string; + teamId: string; + memberUsername: string; + avatar: string; + role: `${TeamMemberRoleEnum}`; + status: `${TeamMemberStatusEnum}`; +}; diff --git a/packages/global/support/user/type.d.ts b/packages/global/support/user/type.d.ts index 9c4b6e852..7372652f8 100644 --- a/packages/global/support/user/type.d.ts +++ b/packages/global/support/user/type.d.ts @@ -1,4 +1,5 @@ -import { InformTypeEnum, TeamMemberRoleEnum } from './constant'; +import { InformTypeEnum } from './constant'; +import { TeamItemType } from './team/type'; export type UserModelSchema = { _id: string; @@ -21,28 +22,13 @@ export type UserModelSchema = { }; }; -export type UserInformSchema = { +export type UserType = { _id: string; - userId: string; - time: Date; - type: `${InformTypeEnum}`; - title: string; - content: string; - read: boolean; -}; - -export type TeamSchema = { - _id: string; - name: string; - ownerId: string; + username: string; avatar: string; - createTime: Date; -}; - -export type TeamMemberSchema = { - _id: string; - name: string; - teamId: string; - userId: string; - role: `${TeamMemberRoleEnum}`; + balance: number; + timezone: string; + promotionRate: UserModelSchema['promotionRate']; + openaiAccount: UserModelSchema['openaiAccount']; + team: TeamItemType; }; diff --git a/packages/global/support/wallet/bill/api.d.ts b/packages/global/support/wallet/bill/api.d.ts new file mode 100644 index 000000000..f24c6d9c6 --- /dev/null +++ b/packages/global/support/wallet/bill/api.d.ts @@ -0,0 +1,25 @@ +import { BillSourceEnum } from './constants'; +import { BillListItemType } from './type'; + +export type CreateTrainingBillProps = { + name: string; +}; + +export type ConcatBillProps = { + teamId: string; + tmbId: string; + billId?: string; + total: number; + listIndex?: number; + tokens?: number; +}; + +export type CreateBillProps = { + teamId: string; + tmbId: string; + appName: string; + appId?: string; + total: number; + source: `${BillSourceEnum}`; + list: BillListItemType[]; +}; diff --git a/packages/global/support/wallet/bill/constants.ts b/packages/global/support/wallet/bill/constants.ts new file mode 100644 index 000000000..bb13498b5 --- /dev/null +++ b/packages/global/support/wallet/bill/constants.ts @@ -0,0 +1,16 @@ +// ¥1 = 100000 +export const PRICE_SCALE = 100000; + +export enum BillSourceEnum { + fastgpt = 'fastgpt', + api = 'api', + shareLink = 'shareLink', + training = 'training' +} + +export const BillSourceMap: Record<`${BillSourceEnum}`, string> = { + [BillSourceEnum.fastgpt]: '在线使用', + [BillSourceEnum.api]: 'Api', + [BillSourceEnum.shareLink]: '免登录链接', + [BillSourceEnum.training]: '数据训练' +}; diff --git a/packages/global/common/bill/tools.ts b/packages/global/support/wallet/bill/tools.ts similarity index 82% rename from packages/global/common/bill/tools.ts rename to packages/global/support/wallet/bill/tools.ts index 6b16b44a2..50db285a7 100644 --- a/packages/global/common/bill/tools.ts +++ b/packages/global/support/wallet/bill/tools.ts @@ -1,5 +1,6 @@ /* bill common */ import { PRICE_SCALE } from './constants'; +import { BillItemType, BillSchema } from './type'; /** * dataset price / PRICE_SCALE = real price diff --git a/packages/global/support/wallet/bill/type.d.ts b/packages/global/support/wallet/bill/type.d.ts new file mode 100644 index 000000000..bacac7646 --- /dev/null +++ b/packages/global/support/wallet/bill/type.d.ts @@ -0,0 +1,24 @@ +import { CreateBillProps } from './api'; +import { BillSourceEnum } from './constants'; + +export type BillListItemType = { + moduleName: string; + amount: number; + model?: string; + tokenLen?: number; +}; + +export type BillSchema = CreateBillProps & { + _id: string; + time: Date; +}; + +export type BillItemType = { + id: string; + username: string; + time: Date; + appName: string; + source: BillSchema['source']; + total: number; + list: BillSchema['list']; +}; diff --git a/packages/global/support/wallet/type.d.ts b/packages/global/support/wallet/pay/type.d.ts similarity index 83% rename from packages/global/support/wallet/type.d.ts rename to packages/global/support/wallet/pay/type.d.ts index 77c89c264..b037a3cd9 100644 --- a/packages/global/support/wallet/type.d.ts +++ b/packages/global/support/wallet/pay/type.d.ts @@ -1,6 +1,8 @@ export type PaySchema = { _id: string; userId: string; + teamId: string; + tmbId: string; createTime: Date; price: number; orderId: string; diff --git a/packages/service/common/api/plusRequest.ts b/packages/service/common/api/plusRequest.ts index a1e47e539..0186ca7b7 100644 --- a/packages/service/common/api/plusRequest.ts +++ b/packages/service/common/api/plusRequest.ts @@ -18,7 +18,6 @@ function requestStart(config: InternalAxiosRequestConfig): InternalAxiosRequestC if (config.headers) { config.headers.rootkey = process.env.ROOT_KEY; } - return config; } @@ -62,7 +61,8 @@ function responseError(err: any) { const instance = axios.create({ timeout: 60000, // 超时时间 headers: { - 'content-type': 'application/json' + 'content-type': 'application/json', + 'Cache-Control': 'no-cache' } }); @@ -73,7 +73,7 @@ instance.interceptors.response.use(responseSuccess, (err) => Promise.reject(err) export function request(url: string, data: any, config: ConfigType, method: Method): any { if (!global.systemEnv?.pluginBaseUrl) { - return Promise.reject('商业版插件加载中...'); + return Promise.reject('该功能为商业版特有...'); } /* 去空 */ diff --git a/packages/service/common/file/gridfs/controller.ts b/packages/service/common/file/gridfs/controller.ts new file mode 100644 index 000000000..17c6a627a --- /dev/null +++ b/packages/service/common/file/gridfs/controller.ts @@ -0,0 +1,111 @@ +import { Types, connectionMongo } from '../../mongo'; +import { BucketNameEnum } from '@fastgpt/global/common/file/constants'; +import fsp from 'fs/promises'; +import fs from 'fs'; +import { DatasetFileSchema } from '@fastgpt/global/core/dataset/type'; + +export function getGFSCollection(bucket: `${BucketNameEnum}`) { + return connectionMongo.connection.db.collection(`${bucket}.files`); +} +export function getGridBucket(bucket: `${BucketNameEnum}`) { + return new connectionMongo.mongo.GridFSBucket(connectionMongo.connection.db, { + bucketName: bucket + }); +} + +/* crud file */ +export async function uploadFile({ + bucketName, + teamId, + tmbId, + path, + filename, + metadata = {} +}: { + bucketName: `${BucketNameEnum}`; + teamId: string; + tmbId: string; + path: string; + filename: string; + metadata?: Record; +}) { + if (!path) return Promise.reject(`filePath is empty`); + if (!filename) return Promise.reject(`filename is empty`); + + const stats = await fsp.stat(path); + if (!stats.isFile()) return Promise.reject(`${path} is not a file`); + + metadata.teamId = teamId; + metadata.tmbId = tmbId; + + // create a gridfs bucket + const bucket = getGridBucket(bucketName); + + const stream = bucket.openUploadStream(filename, { + metadata, + contentType: metadata?.contentType + }); + + // save to gridfs + await new Promise((resolve, reject) => { + fs.createReadStream(path) + .pipe(stream as any) + .on('finish', resolve) + .on('error', reject); + }); + + return String(stream.id); +} + +export async function getFileById({ + bucketName, + fileId +}: { + bucketName: `${BucketNameEnum}`; + fileId: string; +}) { + const db = getGFSCollection(bucketName); + const file = await db.findOne({ + _id: new Types.ObjectId(fileId) + }); + + if (!file) { + return Promise.reject('File not found'); + } + + return file; +} + +export async function delFileById({ + bucketName, + fileId +}: { + bucketName: `${BucketNameEnum}`; + fileId: string; +}) { + const bucket = getGridBucket(bucketName); + + await bucket.delete(new Types.ObjectId(fileId)); + return true; +} + +export async function getDownloadBuf({ + bucketName, + fileId +}: { + bucketName: `${BucketNameEnum}`; + fileId: string; +}) { + const bucket = getGridBucket(bucketName); + + const stream = bucket.openDownloadStream(new Types.ObjectId(fileId)); + + const buf: Buffer = await new Promise((resolve, reject) => { + const buffers: Buffer[] = []; + stream.on('data', (data) => buffers.push(data)); + stream.on('error', reject); + stream.on('end', () => resolve(Buffer.concat(buffers))); + }); + + return buf; +} diff --git a/packages/service/common/mongo/controller.ts b/packages/service/common/mongo/controller.ts new file mode 100644 index 000000000..b07b81fcf --- /dev/null +++ b/packages/service/common/mongo/controller.ts @@ -0,0 +1,26 @@ +/* add logger */ +export const addLog = { + info: (msg: string, obj?: Record) => { + global.logger?.info(msg, { meta: obj }); + }, + error: (msg: string, error?: any) => { + global.logger?.error(msg, { + meta: { + stack: error?.stack, + ...(error?.config && { + config: { + headers: error.config.headers, + url: error.config.url, + data: error.config.data + } + }), + ...(error?.response && { + response: { + status: error.response.status, + statusText: error.response.statusText + } + }) + } + }); + } +}; diff --git a/packages/service/common/mongo/init.ts b/packages/service/common/mongo/init.ts index de2b7ed98..0025cfb72 100644 --- a/packages/service/common/mongo/init.ts +++ b/packages/service/common/mongo/init.ts @@ -1,6 +1,6 @@ import mongoose from './index'; -import 'winston-mongodb'; import { createLogger, format, transports } from 'winston'; +import 'winston-mongodb'; /** * connect MongoDB and init data @@ -19,9 +19,6 @@ export async function connectMongo({ beforeHook && (await beforeHook()); - // logger - initLogger(); - console.log('mongo start connect'); try { mongoose.set('strictQuery', true); @@ -35,9 +32,11 @@ export async function connectMongo({ }); console.log('mongo connected'); + initLogger(); afterHook && (await afterHook()); } catch (error) { + global.mongodb.disconnect(); console.log('error->', 'mongo connect error', error); global.mongodb = undefined; } diff --git a/packages/service/common/mongo/sessionRun.ts b/packages/service/common/mongo/sessionRun.ts index a31074635..b9fc57e5e 100644 --- a/packages/service/common/mongo/sessionRun.ts +++ b/packages/service/common/mongo/sessionRun.ts @@ -1,39 +1,21 @@ -import mongoose from './index'; +import mongoose, { connectionMongo } from './index'; -export class MongoSession { - tasks: (() => Promise)[] = []; - session: mongoose.mongo.ClientSession | null = null; - opts: { - session: mongoose.mongo.ClientSession; - new: boolean; - } | null = null; +export async function mongoSessionTask( + fn: (session: mongoose.mongo.ClientSession) => Promise +) { + const session = await connectionMongo.startSession(); - constructor() {} - async init() { - this.session = await mongoose.startSession(); - this.opts = { session: this.session, new: true }; - } - push( - tasks: ((opts: { - session: mongoose.mongo.ClientSession; - new: boolean; - }) => () => Promise)[] = [] - ) { - if (!this.opts) return; - // this.tasks = this.tasks.concat(tasks.map((item) => item(this.opts))); - } - async run() { - if (!this.session || !this.opts) return; - try { - this.session.startTransaction(); + try { + session.startTransaction(); - const opts = { session: this.session, new: true }; + await fn(session); - await this.session.commitTransaction(); - } catch (error) { - await this.session.abortTransaction(); - console.error(error); - } - this.session.endSession(); + await session.commitTransaction(); + await session.endSession(); + } catch (error) { + await session.abortTransaction(); + await session.endSession(); + console.error(error); + return Promise.reject(error); } } diff --git a/projects/app/src/service/pg.ts b/packages/service/common/pg/index.ts similarity index 88% rename from projects/app/src/service/pg.ts rename to packages/service/common/pg/index.ts index fb80aeaf8..cc9177ffb 100644 --- a/projects/app/src/service/pg.ts +++ b/packages/service/common/pg/index.ts @@ -1,7 +1,6 @@ import { Pool } from 'pg'; import type { QueryResultRow } from 'pg'; -import { PgDatasetTableName } from '@/constants/plugin'; -import { DatasetSpecialIdEnum } from '@fastgpt/global/core/dataset/constant'; +import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant'; export const connectPg = async (): Promise => { if (global.pgClient) { @@ -55,7 +54,7 @@ type InsertProps = { values: ValuesProps[]; }; -class Pg { +class PgClass { private getWhereStr(where?: WhereProps) { return where ? `WHERE ${where @@ -160,27 +159,6 @@ class Pg { } } -export const PgClient = new Pg(); - -/** - * Update data file_id - */ -export const updateDataFileId = async ({ - oldFileId, - userId, - newFileId = DatasetSpecialIdEnum.manual -}: { - oldFileId: string; - userId: string; - newFileId?: string; -}) => { - await PgClient.update(PgDatasetTableName, { - where: [['file_id', oldFileId], 'AND', ['user_id', userId]], - values: [{ key: 'file_id', value: newFileId }] - }); - return newFileId; -}; - export async function initPg() { try { await connectPg(); @@ -189,13 +167,14 @@ export async function initPg() { CREATE TABLE IF NOT EXISTS ${PgDatasetTableName} ( id BIGSERIAL PRIMARY KEY, vector VECTOR(1536) NOT NULL, - user_id VARCHAR(50) NOT NULL, + team_id VARCHAR(50) NOT NULL, + tmb_id VARCHAR(50) NOT NULL, dataset_id VARCHAR(50) NOT NULL, collection_id VARCHAR(50) NOT NULL, q TEXT NOT NULL, a TEXT ); - CREATE INDEX IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 24, ef_construction = 48); + CREATE INDEX IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 24, ef_construction = 64); `); console.log('init pg successful'); @@ -203,3 +182,6 @@ export async function initPg() { console.log('init pg error', error); } } + +export const PgClient = new PgClass(); +export const Pg = global.pgClient; diff --git a/packages/service/common/pg/type.d.ts b/packages/service/common/pg/type.d.ts new file mode 100644 index 000000000..fed5fc8b7 --- /dev/null +++ b/packages/service/common/pg/type.d.ts @@ -0,0 +1,5 @@ +import type { Pool } from 'pg'; + +declare global { + var pgClient: Pool | null; +} diff --git a/packages/service/common/response/constant.ts b/packages/service/common/response/constant.ts new file mode 100644 index 000000000..23a5fa81c --- /dev/null +++ b/packages/service/common/response/constant.ts @@ -0,0 +1,6 @@ +export enum sseResponseEventEnum { + error = 'error', + answer = 'answer', + moduleStatus = 'moduleStatus', + appStreamResponse = 'appStreamResponse' // sse response request +} diff --git a/packages/service/common/response/index.ts b/packages/service/common/response/index.ts index 5e887b96e..323b9764c 100644 --- a/packages/service/common/response/index.ts +++ b/packages/service/common/response/index.ts @@ -1,4 +1,98 @@ import type { NextApiResponse } from 'next'; +import { sseResponseEventEnum } from './constant'; +import { proxyError, ERROR_RESPONSE, ERROR_ENUM } from '@fastgpt/global/common/error/errorCode'; +import { addLog } from '../mongo/controller'; +import { clearCookie } from '../../support/permission/controller'; + +export interface ResponseType { + code: number; + message: string; + data: T; +} + +export const jsonRes = ( + res: NextApiResponse, + props?: { + code?: number; + message?: string; + data?: T; + error?: any; + } +) => { + const { code = 200, message = '', data = null, error } = props || {}; + + const errResponseKey = typeof error === 'string' ? error : error?.message; + // Specified error + if (ERROR_RESPONSE[errResponseKey]) { + // login is expired + if (errResponseKey === ERROR_ENUM.unAuthorization) { + clearCookie(res); + } + + return res.json(ERROR_RESPONSE[errResponseKey]); + } + + // another error + let msg = ''; + if ((code < 200 || code >= 400) && !message) { + msg = error?.response?.statusText || error?.message || '请求错误'; + if (typeof error === 'string') { + msg = error; + } else if (proxyError[error?.code]) { + msg = '网络连接异常'; + } else if (error?.response?.data?.error?.message) { + msg = error?.response?.data?.error?.message; + } else if (error?.error?.message) { + msg = error?.error?.message; + } + + addLog.error(`response error: ${msg}`, error); + } + + res.status(code).json({ + code, + statusText: '', + message: message || msg, + data: data !== undefined ? data : null + }); +}; + +export const sseErrRes = (res: NextApiResponse, error: any) => { + const errResponseKey = typeof error === 'string' ? error : error?.message; + + // Specified error + if (ERROR_RESPONSE[errResponseKey]) { + // login is expired + if (errResponseKey === ERROR_ENUM.unAuthorization) { + clearCookie(res); + } + + return responseWrite({ + res, + event: sseResponseEventEnum.error, + data: JSON.stringify(ERROR_RESPONSE[errResponseKey]) + }); + } + + let msg = error?.response?.statusText || error?.message || '请求错误'; + if (typeof error === 'string') { + msg = error; + } else if (proxyError[error?.code]) { + msg = '网络连接异常'; + } else if (error?.response?.data?.error?.message) { + msg = error?.response?.data?.error?.message; + } else if (error?.error?.message) { + msg = error?.error?.message; + } + + addLog.error(`sse error: ${msg}`, error); + + responseWrite({ + res, + event: sseResponseEventEnum.error, + data: JSON.stringify({ message: msg }) + }); +}; export function responseWriteController({ res, diff --git a/packages/service/core/ai/audio/speech.ts b/packages/service/core/ai/audio/speech.ts new file mode 100644 index 000000000..a3e09e029 --- /dev/null +++ b/packages/service/core/ai/audio/speech.ts @@ -0,0 +1,26 @@ +import { Text2SpeechProps } from '@fastgpt/global/core/ai/speech/api'; +import { getAIApi } from '../config'; +import { defaultAudioSpeechModels } from '../../../../global/core/ai/model'; +import { Text2SpeechVoiceEnum } from '@fastgpt/global/core/ai/speech/constant'; + +export async function text2Speech({ + model = defaultAudioSpeechModels[0].model, + voice = Text2SpeechVoiceEnum.alloy, + input, + speed = 1 +}: Text2SpeechProps) { + const ai = getAIApi(); + const mp3 = await ai.audio.speech.create({ + model, + voice, + input, + response_format: 'mp3', + speed + }); + const buffer = Buffer.from(await mp3.arrayBuffer()); + return { + model, + voice, + tts: buffer + }; +} diff --git a/packages/service/core/ai/config.ts b/packages/service/core/ai/config.ts index 5b41e2e7f..3c443db01 100644 --- a/packages/service/core/ai/config.ts +++ b/packages/service/core/ai/config.ts @@ -1,5 +1,5 @@ import type { UserModelSchema } from '@fastgpt/global/support/user/type'; -import OpenAI from 'openai'; +import OpenAI from '@fastgpt/global/core/ai'; export const openaiBaseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1'; export const baseUrl = process.env.ONEAPI_URL || openaiBaseUrl; diff --git a/packages/service/core/ai/functions/createQuestionGuide.ts b/packages/service/core/ai/functions/createQuestionGuide.ts index f69d2acb8..69f3813f2 100644 --- a/packages/service/core/ai/functions/createQuestionGuide.ts +++ b/packages/service/core/ai/functions/createQuestionGuide.ts @@ -1,4 +1,4 @@ -import type { ChatCompletionRequestMessage } from '@fastgpt/global/core/ai/type.d'; +import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d'; import { getAIApi } from '../config'; export const Prompt_QuestionGuide = `我不太清楚问你什么问题,请帮我生成 3 个问题,引导我继续提问。问题的长度应小于20个字符,按 JSON 格式返回: ["问题1", "问题2", "问题3"]`; @@ -7,7 +7,7 @@ export async function createQuestionGuide({ messages, model }: { - messages: ChatCompletionRequestMessage[]; + messages: ChatMessageItemType[]; model: string; }) { const ai = getAIApi(undefined, 48000); diff --git a/packages/service/core/app/schema.ts b/packages/service/core/app/schema.ts new file mode 100644 index 000000000..dd32113fc --- /dev/null +++ b/packages/service/core/app/schema.ts @@ -0,0 +1,70 @@ +import { connectionMongo, type Model } from '../../common/mongo'; +const { Schema, model, models } = connectionMongo; +import type { AppSchema as AppType } from '@fastgpt/global/core/app/type.d'; +import { PermissionTypeEnum, PermissionTypeMap } from '@fastgpt/global/support/permission/constant'; +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; + +export const appCollectionName = 'apps'; + +const AppSchema = new Schema({ + userId: { + type: Schema.Types.ObjectId, + ref: 'user' + }, + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, + required: true + }, + name: { + type: String, + required: true + }, + type: { + type: String, + default: 'advanced', + enum: ['basic', 'advanced'] + }, + avatar: { + type: String, + default: '/icon/logo.svg' + }, + intro: { + type: String, + default: '' + }, + updateTime: { + type: Date, + default: () => new Date() + }, + modules: { + type: Array, + default: [] + }, + inited: { + type: Boolean + }, + permission: { + type: String, + enum: Object.keys(PermissionTypeMap), + default: PermissionTypeEnum.private + } +}); + +try { + AppSchema.index({ updateTime: -1 }); + AppSchema.index({ 'share.collection': -1 }); +} catch (error) { + console.log(error); +} + +export const MongoApp: Model = + models[appCollectionName] || model(appCollectionName, AppSchema); diff --git a/projects/app/src/service/models/chatItem.ts b/packages/service/core/chat/chatItemSchema.ts similarity index 61% rename from projects/app/src/service/models/chatItem.ts rename to packages/service/core/chat/chatItemSchema.ts index 0cb9807e4..ee7ccdf1d 100644 --- a/projects/app/src/service/models/chatItem.ts +++ b/packages/service/core/chat/chatItemSchema.ts @@ -1,9 +1,15 @@ -import { connectionMongo, type Model } from '@fastgpt/service/common/mongo'; +import { connectionMongo, type Model } from '../../common/mongo'; const { Schema, model, models } = connectionMongo; -import { ChatItemSchema as ChatItemType } from '@/types/mongoSchema'; -import { ChatRoleMap, TaskResponseKeyEnum } from '@/constants/chat'; +import { ChatItemSchema as ChatItemType } from '@fastgpt/global/core/chat/type'; +import { ChatRoleMap, TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; import { customAlphabet } from 'nanoid'; const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24); +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; +import { appCollectionName } from '../app/schema'; +import { userCollectionName } from '../../support/user/schema'; const ChatItemSchema = new Schema({ dataId: { @@ -11,18 +17,27 @@ const ChatItemSchema = new Schema({ require: true, default: () => nanoid() }, + appId: { + type: Schema.Types.ObjectId, + ref: appCollectionName, + required: true + }, chatId: { type: String, require: true }, userId: { type: Schema.Types.ObjectId, - ref: 'user', + ref: userCollectionName + }, + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, required: true }, - appId: { + tmbId: { type: Schema.Types.ObjectId, - ref: 'model', + ref: TeamMemberCollectionName, required: true }, time: { @@ -53,6 +68,9 @@ const ChatItemSchema = new Schema({ [TaskResponseKeyEnum.responseData]: { type: Array, default: [] + }, + tts: { + type: Buffer } }); @@ -66,5 +84,5 @@ try { console.log(error); } -export const ChatItem: Model = +export const MongoChatItem: Model = models['chatItem'] || model('chatItem', ChatItemSchema); diff --git a/projects/app/src/service/models/chat.ts b/packages/service/core/chat/chatSchema.ts similarity index 61% rename from projects/app/src/service/models/chat.ts rename to packages/service/core/chat/chatSchema.ts index fff7fd7dc..71cd37729 100644 --- a/projects/app/src/service/models/chat.ts +++ b/packages/service/core/chat/chatSchema.ts @@ -1,8 +1,18 @@ -import { connectionMongo, type Model } from '@fastgpt/service/common/mongo'; +import { connectionMongo, type Model } from '../../common/mongo'; const { Schema, model, models } = connectionMongo; -import { ChatSchema as ChatType } from '@/types/mongoSchema'; -import { ChatRoleMap, TaskResponseKeyEnum } from '@/constants/chat'; -import { ChatSourceMap } from '@/constants/chat'; +import { ChatSchema as ChatType } from '@fastgpt/global/core/chat/type.d'; +import { + ChatRoleMap, + ChatSourceMap, + TaskResponseKeyEnum +} from '@fastgpt/global/core/chat/constants'; +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; +import { appCollectionName } from '../app/schema'; + +export const chatCollectionName = 'chat'; const ChatSchema = new Schema({ chatId: { @@ -11,12 +21,21 @@ const ChatSchema = new Schema({ }, userId: { type: Schema.Types.ObjectId, - ref: 'user', + ref: 'user' + }, + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, required: true }, appId: { type: Schema.Types.ObjectId, - ref: 'model', + ref: appCollectionName, required: true }, updateTime: { @@ -80,4 +99,5 @@ try { console.log(error); } -export const Chat: Model = models['chat'] || model('chat', ChatSchema); +export const MongoChat: Model = + models[chatCollectionName] || model(chatCollectionName, ChatSchema); diff --git a/packages/service/core/dataset/auth.ts b/packages/service/core/dataset/auth.ts deleted file mode 100644 index 3f30888eb..000000000 --- a/packages/service/core/dataset/auth.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode'; -import { MongoDatasetCollection } from './collection/schema'; -import { DatasetSchemaType } from '@fastgpt/global/core/dataset/type'; - -export async function authCollection({ - collectionId, - userId -}: { - collectionId: string; - userId: string; -}) { - const collection = await MongoDatasetCollection.findOne({ - _id: collectionId, - userId - }) - .populate('datasetId') - .lean(); - - if (collection) { - return { - ...collection, - dataset: collection.datasetId as unknown as DatasetSchemaType - }; - } - return Promise.reject(ERROR_ENUM.unAuthDataset); -} diff --git a/packages/service/core/dataset/collection/schema.ts b/packages/service/core/dataset/collection/schema.ts index 9bd37230c..52800de12 100644 --- a/packages/service/core/dataset/collection/schema.ts +++ b/packages/service/core/dataset/collection/schema.ts @@ -3,6 +3,10 @@ const { Schema, model, models } = connectionMongo; import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type.d'; import { DatasetCollectionTypeMap } from '@fastgpt/global/core/dataset/constant'; import { DatasetCollectionName } from '../schema'; +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; export const DatasetColCollectionName = 'dataset.collections'; @@ -13,8 +17,18 @@ const DatasetCollectionSchema = new Schema({ default: null }, userId: { + // abandoned type: Schema.Types.ObjectId, - ref: 'user', + ref: 'user' + }, + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, required: true }, datasetId: { diff --git a/packages/service/core/dataset/collection/utils.ts b/packages/service/core/dataset/collection/utils.ts index a3fd075fe..a3fc7937a 100644 --- a/packages/service/core/dataset/collection/utils.ts +++ b/packages/service/core/dataset/collection/utils.ts @@ -31,18 +31,16 @@ export async function findCollectionAndChild(id: string, fields = '_id parentId } export async function getDatasetCollectionPaths({ - parentId = '', - userId + parentId = '' }: { parentId?: string; - userId: string; }): Promise { async function find(parentId?: string): Promise { if (!parentId) { return []; } - const parent = await MongoDatasetCollection.findOne({ _id: parentId, userId }, 'name parentId'); + const parent = await MongoDatasetCollection.findOne({ _id: parentId }, 'name parentId'); if (!parent) return []; diff --git a/packages/service/core/dataset/controller.ts b/packages/service/core/dataset/controller.ts new file mode 100644 index 000000000..f6f9ed5b1 --- /dev/null +++ b/packages/service/core/dataset/controller.ts @@ -0,0 +1,12 @@ +import { CollectionWithDatasetType } from '@fastgpt/global/core/dataset/type'; +import { MongoDatasetCollection } from './collection/schema'; + +export async function getCollectionWithDataset(collectionId: string) { + const data = ( + await MongoDatasetCollection.findById(collectionId).populate('datasetId') + )?.toJSON() as CollectionWithDatasetType; + if (!data) { + return Promise.reject('Collection is not exist'); + } + return data; +} diff --git a/packages/service/core/dataset/file/controller.ts b/packages/service/core/dataset/file/controller.ts new file mode 100644 index 000000000..bd3c38b09 --- /dev/null +++ b/packages/service/core/dataset/file/controller.ts @@ -0,0 +1,9 @@ +import { BucketNameEnum } from '@fastgpt/global/common/file/constants'; +import { getGFSCollection } from '../../../common/file/gridfs/controller'; + +export async function delDatasetFiles({ datasetId }: { datasetId: string }) { + const db = getGFSCollection(BucketNameEnum.dataset); + await db.deleteMany({ + 'metadata.datasetId': String(datasetId) + }); +} diff --git a/packages/service/core/dataset/schema.ts b/packages/service/core/dataset/schema.ts index 793c98626..937896829 100644 --- a/packages/service/core/dataset/schema.ts +++ b/packages/service/core/dataset/schema.ts @@ -2,6 +2,11 @@ import { connectionMongo, type Model } from '../../common/mongo'; const { Schema, model, models } = connectionMongo; import { DatasetSchemaType } from '@fastgpt/global/core/dataset/type.d'; import { DatasetTypeMap } from '@fastgpt/global/core/dataset/constant'; +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; +import { PermissionTypeEnum, PermissionTypeMap } from '@fastgpt/global/support/permission/constant'; export const DatasetCollectionName = 'datasets'; @@ -12,8 +17,18 @@ const DatasetSchema = new Schema({ default: null }, userId: { + //abandon type: Schema.Types.ObjectId, - ref: 'user', + ref: 'user' + }, + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, required: true }, updateTime: { @@ -41,7 +56,16 @@ const DatasetSchema = new Schema({ }, tags: { type: [String], - default: [] + default: [], + set(val: string | string[]) { + if (Array.isArray(val)) return val; + return val.split(' ').filter((item) => item); + } + }, + permission: { + type: String, + enum: Object.keys(PermissionTypeMap), + default: PermissionTypeEnum.private } }); diff --git a/packages/service/core/dataset/training/schema.ts b/packages/service/core/dataset/training/schema.ts index b39c9a656..249601fdc 100644 --- a/packages/service/core/dataset/training/schema.ts +++ b/packages/service/core/dataset/training/schema.ts @@ -5,13 +5,27 @@ import { DatasetTrainingSchemaType } from '@fastgpt/global/core/dataset/type'; import { TrainingTypeMap } from '@fastgpt/global/core/dataset/constant'; import { DatasetColCollectionName } from '../collection/schema'; import { DatasetCollectionName } from '../schema'; +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; export const DatasetTrainingCollectionName = 'dataset.trainings'; const TrainingDataSchema = new Schema({ userId: { + // abandon type: Schema.Types.ObjectId, - ref: 'user', + ref: 'user' + }, + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, required: true }, datasetId: { diff --git a/packages/service/core/plugin/controller.ts b/packages/service/core/plugin/controller.ts index 4f8deb2e2..64b8a8273 100644 --- a/packages/service/core/plugin/controller.ts +++ b/packages/service/core/plugin/controller.ts @@ -1,38 +1,15 @@ -import { CreateOnePluginParams, UpdatePluginParams } from '@fastgpt/global/core/plugin/controller'; import { MongoPlugin } from './schema'; import { FlowModuleTemplateType } from '@fastgpt/global/core/module/type'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant'; import { formatPluginIOModules } from '@fastgpt/global/core/module/utils'; -export async function createOnePlugin(data: CreateOnePluginParams & { userId: string }) { - const { _id } = await MongoPlugin.create(data); - return _id; -} - -export async function updateOnePlugin({ - id, - userId, - ...data -}: UpdatePluginParams & { userId: string }) { - await MongoPlugin.findOneAndUpdate({ _id: id, userId }, data); -} - -export async function deleteOnePlugin({ id, userId }: { id: string; userId: string }) { - await MongoPlugin.findOneAndDelete({ _id: id, userId }); -} -export async function getUserPlugins({ userId }: { userId: string }) { - return MongoPlugin.find({ userId }, 'name avatar intro'); -} -export async function getOnePluginDetail({ id, userId }: { userId: string; id: string }) { - return MongoPlugin.findOne({ _id: id, userId }); -} /* plugin templates */ export async function getUserPlugins2Templates({ - userId + teamId }: { - userId: string; + teamId: string; }): Promise { - const plugins = await MongoPlugin.find({ userId }).lean(); + const plugins = await MongoPlugin.find({ teamId }).lean(); return plugins.map((plugin) => ({ id: String(plugin._id), @@ -47,8 +24,8 @@ export async function getUserPlugins2Templates({ })); } /* one plugin 2 module detail */ -export async function getPluginModuleDetail({ id, userId }: { userId: string; id: string }) { - const plugin = await getOnePluginDetail({ id, userId }); +export async function getPluginModuleDetail({ id }: { id: string }) { + const plugin = await MongoPlugin.findById(id); if (!plugin) return Promise.reject('plugin not found'); return { id: String(plugin._id), diff --git a/packages/service/core/plugin/schema.ts b/packages/service/core/plugin/schema.ts index 76ab05432..2aff509b8 100644 --- a/packages/service/core/plugin/schema.ts +++ b/packages/service/core/plugin/schema.ts @@ -1,13 +1,26 @@ import { connectionMongo, type Model } from '../../common/mongo'; const { Schema, model, models } = connectionMongo; import type { PluginItemSchema } from '@fastgpt/global/core/plugin/type.d'; +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; export const ModuleCollectionName = 'plugins'; const PluginSchema = new Schema({ userId: { type: Schema.Types.ObjectId, - ref: 'user', + ref: 'user' + }, + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, required: true }, name: { diff --git a/packages/service/package.json b/packages/service/package.json index d39fc5333..db89876fc 100644 --- a/packages/service/package.json +++ b/packages/service/package.json @@ -13,10 +13,12 @@ "winston-mongodb": "^5.1.1", "tunnel": "^0.0.6", "encoding": "^0.1.13", - "openai": "^4.12.4" + "pg": "^8.10.0", + "nanoid": "^4.0.1" }, "devDependencies": { "@types/tunnel": "^0.0.4", + "@types/pg": "^8.6.6", "@types/node": "^20.8.5", "@types/cookie": "^0.5.2", "@types/jsonwebtoken": "^9.0.3" diff --git a/packages/service/support/openapi/auth.ts b/packages/service/support/openapi/auth.ts index 935f278ec..17e5fe4b7 100644 --- a/packages/service/support/openapi/auth.ts +++ b/packages/service/support/openapi/auth.ts @@ -10,13 +10,11 @@ export async function authOpenApiKey({ apikey }: { apikey: string }) { if (!apikey) { return Promise.reject(ERROR_ENUM.unAuthApiKey); } - try { const openApi = await MongoOpenApi.findOne({ apiKey: apikey }); if (!openApi) { return Promise.reject(ERROR_ENUM.unAuthApiKey); } - const userId = String(openApi.userId); // auth limit if (global.feConfigs?.isPlus) { @@ -25,7 +23,13 @@ export async function authOpenApiKey({ apikey }: { apikey: string }) { updateApiKeyUsedTime(openApi._id); - return { apikey, userId, appId: openApi.appId }; + return { + apikey, + userId: String(openApi.userId), + teamId: String(openApi.teamId), + tmbId: String(openApi.tmbId), + appId: openApi.appId || '' + }; } catch (error) { return Promise.reject(error); } diff --git a/packages/service/support/openapi/schema.ts b/packages/service/support/openapi/schema.ts index a82c16225..ebedc52dd 100644 --- a/packages/service/support/openapi/schema.ts +++ b/packages/service/support/openapi/schema.ts @@ -1,14 +1,27 @@ import { connectionMongo, type Model } from '../../common/mongo'; const { Schema, model, models } = connectionMongo; import type { OpenApiSchema } from '@fastgpt/global/support/openapi/type'; -import { PRICE_SCALE } from '@fastgpt/global/common/bill/constants'; -import { formatPrice } from '@fastgpt/global/common/bill/tools'; +import { PRICE_SCALE } from '@fastgpt/global/support/wallet/bill/constants'; +import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; const OpenApiSchema = new Schema( { userId: { type: Schema.Types.ObjectId, - ref: 'user', + ref: 'user' + }, + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, required: true }, apiKey: { diff --git a/packages/service/support/outLink/auth.ts b/packages/service/support/outLink/auth.ts deleted file mode 100644 index edf186615..000000000 --- a/packages/service/support/outLink/auth.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { AuthUserTypeEnum, authBalanceByUid } from '../user/auth'; -import { MongoOutLink } from './schema'; -import { POST } from '../../common/api/plusRequest'; -import { OutLinkSchema } from '@fastgpt/global/support/outLink/type'; - -export type AuthLinkProps = { ip?: string | null; authToken?: string; question: string }; -export type AuthLinkLimitProps = AuthLinkProps & { outLink: OutLinkSchema }; - -export async function authOutLinkChat({ - shareId, - ip, - authToken, - question -}: AuthLinkProps & { - shareId: string; -}) { - // get outLink - const outLink = await MongoOutLink.findOne({ - shareId - }); - - if (!outLink) { - return Promise.reject('分享链接无效'); - } - - const uid = String(outLink.userId); - - const [user] = await Promise.all([ - authBalanceByUid(uid), // authBalance - ...(global.feConfigs?.isPlus ? [authOutLinkLimit({ outLink, ip, authToken, question })] : []) // limit auth - ]); - - return { - user, - userId: String(outLink.userId), - appId: String(outLink.appId), - authType: AuthUserTypeEnum.token, - responseDetail: outLink.responseDetail - }; -} - -export function authOutLinkLimit(data: AuthLinkLimitProps) { - return POST('/support/outLink/authLimit', data); -} - -export async function authOutLinkId({ id }: { id: string }) { - const outLink = await MongoOutLink.findOne({ - shareId: id - }); - - if (!outLink) { - return Promise.reject('分享链接无效'); - } - - return { - userId: String(outLink.userId) - }; -} - -export type AuthShareChatInitProps = { - authToken?: string; - tokenUrl?: string; -}; - -export function authShareChatInit(data: AuthShareChatInitProps) { - if (!global.feConfigs?.isPlus) return; - return POST('/support/outLink/authShareChatInit', data); -} diff --git a/packages/service/support/outLink/schema.ts b/packages/service/support/outLink/schema.ts index d8af1f42f..85b15fece 100644 --- a/packages/service/support/outLink/schema.ts +++ b/packages/service/support/outLink/schema.ts @@ -2,6 +2,10 @@ import { connectionMongo, type Model } from '../../common/mongo'; const { Schema, model, models } = connectionMongo; import { OutLinkSchema as SchemaType } from '@fastgpt/global/support/outLink/type'; import { OutLinkTypeEnum } from '@fastgpt/global/support/outLink/constant'; +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; const OutLinkSchema = new Schema({ shareId: { @@ -10,7 +14,16 @@ const OutLinkSchema = new Schema({ }, userId: { type: Schema.Types.ObjectId, - ref: 'user', + ref: 'user' + }, + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, required: true }, appId: { diff --git a/packages/service/support/permission/auth/app.ts b/packages/service/support/permission/auth/app.ts new file mode 100644 index 000000000..a15211f47 --- /dev/null +++ b/packages/service/support/permission/auth/app.ts @@ -0,0 +1,72 @@ +import { MongoApp } from '../../../core/app/schema'; +import { AppDetailType } from '@fastgpt/global/core/app/type.d'; +import { AuthModeType } from '../type'; +import { AuthResponseType } from '@fastgpt/global/support/permission/type'; +import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; +import { parseHeaderCert } from '../controller'; +import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { AppErrEnum } from '@fastgpt/global/common/error/code/app'; +import { getTeamInfoByTmbId } from '../../user/team/controller'; + +// 模型使用权校验 +export async function authApp({ + appId, + per = 'owner', + ...props +}: AuthModeType & { + appId: string; +}): Promise< + AuthResponseType & { + teamOwner: boolean; + app: AppDetailType; + } +> { + const result = await parseHeaderCert(props); + const { userId, teamId, tmbId } = result; + const { role } = await getTeamInfoByTmbId({ tmbId }); + + const { app, isOwner, canWrite } = await (async () => { + // get app + const app = (await MongoApp.findOne({ _id: appId, teamId }))?.toJSON(); + if (!app) { + return Promise.reject(AppErrEnum.unAuthApp); + } + + const isOwner = + role !== TeamMemberRoleEnum.visitor && + (String(app.tmbId) === tmbId || role === TeamMemberRoleEnum.owner); + const canWrite = + isOwner || + (app.permission === PermissionTypeEnum.public && role !== TeamMemberRoleEnum.visitor); + + if (per === 'r') { + if (!isOwner && app.permission !== PermissionTypeEnum.public) { + return Promise.reject(AppErrEnum.unAuthApp); + } + } + if (per === 'w' && !canWrite) { + return Promise.reject(AppErrEnum.unAuthApp); + } + if (per === 'owner' && !isOwner) { + return Promise.reject(AppErrEnum.unAuthApp); + } + + return { + app: { + ...app, + isOwner, + canWrite + }, + isOwner, + canWrite + }; + })(); + + return { + ...result, + app, + isOwner, + canWrite, + teamOwner: role === TeamMemberRoleEnum.owner + }; +} diff --git a/packages/service/support/permission/auth/chat.ts b/packages/service/support/permission/auth/chat.ts new file mode 100644 index 000000000..ddad76e61 --- /dev/null +++ b/packages/service/support/permission/auth/chat.ts @@ -0,0 +1,67 @@ +import { AuthResponseType } from '@fastgpt/global/support/permission/type'; +import { AuthModeType } from '../type'; +import type { ChatSchema, ChatWithAppSchema } from '@fastgpt/global/core/chat/type'; +import { parseHeaderCert } from '../controller'; +import { MongoChat } from '../../../core/chat/chatSchema'; +import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; +import { getTeamInfoByTmbId } from '../../user/team/controller'; +import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; +import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; + +export async function authChat({ + chatId, + per = 'owner', + ...props +}: AuthModeType & { + chatId: string; +}): Promise< + AuthResponseType & { + chat: ChatSchema; + } +> { + const { userId, teamId, tmbId } = await parseHeaderCert(props); + const { role } = await getTeamInfoByTmbId({ tmbId }); + + const { chat, isOwner, canWrite } = await (async () => { + // get chat + const chat = ( + await MongoChat.findOne({ chatId, teamId }).populate('appId') + )?.toJSON() as ChatWithAppSchema; + + if (!chat) { + return Promise.reject('Chat is not exists'); + } + + const isOwner = role === TeamMemberRoleEnum.owner || String(chat.tmbId) === tmbId; + const canWrite = isOwner; + + if ( + per === 'r' && + role !== TeamMemberRoleEnum.owner && + chat.appId.permission !== PermissionTypeEnum.public + ) { + return Promise.reject(ChatErrEnum.unAuthChat); + } + if (per === 'w' && !canWrite) { + return Promise.reject(ChatErrEnum.unAuthChat); + } + if (per === 'owner' && !isOwner) { + return Promise.reject(ChatErrEnum.unAuthChat); + } + + return { + chat, + isOwner, + canWrite + }; + })(); + + return { + userId, + teamId, + tmbId, + chat, + isOwner, + canWrite + }; +} diff --git a/packages/service/support/permission/auth/common.ts b/packages/service/support/permission/auth/common.ts new file mode 100644 index 000000000..26f9091b5 --- /dev/null +++ b/packages/service/support/permission/auth/common.ts @@ -0,0 +1,12 @@ +import { parseHeaderCert } from '../controller'; +import { AuthModeType } from '../type'; + +export const authCert = async (props: AuthModeType) => { + const result = await parseHeaderCert(props); + + return { + ...result, + isOwner: true, + canWrite: true + }; +}; diff --git a/packages/service/support/permission/auth/dataset.ts b/packages/service/support/permission/auth/dataset.ts new file mode 100644 index 000000000..e4c32fb5e --- /dev/null +++ b/packages/service/support/permission/auth/dataset.ts @@ -0,0 +1,181 @@ +import { AuthModeType } from '../type'; +import { parseHeaderCert } from '../controller'; +import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset'; +import { MongoDataset } from '../../../core/dataset/schema'; +import { getCollectionWithDataset } from '../../../core/dataset/controller'; +import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; +import { AuthResponseType } from '@fastgpt/global/support/permission/type'; +import { + CollectionWithDatasetType, + DatasetFileSchema, + DatasetSchemaType +} from '@fastgpt/global/core/dataset/type'; +import { getFileById } from '../../../common/file/gridfs/controller'; +import { BucketNameEnum } from '@fastgpt/global/common/file/constants'; +import { getTeamInfoByTmbId } from '../../user/team/controller'; + +export async function authDataset({ + datasetId, + per = 'owner', + ...props +}: AuthModeType & { + datasetId: string; +}): Promise< + AuthResponseType & { + dataset: DatasetSchemaType; + } +> { + const result = await parseHeaderCert(props); + const { userId, teamId, tmbId } = result; + const { role } = await getTeamInfoByTmbId({ tmbId }); + + const { dataset, isOwner, canWrite } = await (async () => { + const dataset = (await MongoDataset.findOne({ _id: datasetId, teamId }))?.toJSON(); + + if (!dataset) { + return Promise.reject(DatasetErrEnum.unAuthDataset); + } + + const isOwner = + role !== TeamMemberRoleEnum.visitor && + (String(dataset.tmbId) === tmbId || role === TeamMemberRoleEnum.owner); + const canWrite = + isOwner || + (role !== TeamMemberRoleEnum.visitor && dataset.permission === PermissionTypeEnum.public); + if (per === 'r') { + if (!isOwner && dataset.permission !== PermissionTypeEnum.public) { + return Promise.reject(DatasetErrEnum.unAuthDataset); + } + } + if (per === 'w' && !canWrite) { + return Promise.reject(DatasetErrEnum.unAuthDataset); + } + if (per === 'owner' && !isOwner) { + return Promise.reject(DatasetErrEnum.unAuthDataset); + } + + return { dataset, isOwner, canWrite }; + })(); + + return { + ...result, + dataset, + isOwner, + canWrite + }; +} + +/* + Read: in team and dataset permission is public + Write: in team, not visitor and dataset permission is public +*/ +export async function authDatasetCollection({ + collectionId, + per = 'owner', + ...props +}: AuthModeType & { + collectionId: string; +}): Promise< + AuthResponseType & { + collection: CollectionWithDatasetType; + } +> { + const { userId, teamId, tmbId } = await parseHeaderCert(props); + const { role } = await getTeamInfoByTmbId({ tmbId }); + + const { collection, isOwner, canWrite } = await (async () => { + const collection = await getCollectionWithDataset(collectionId); + + if (!collection || String(collection.teamId) !== teamId) { + return Promise.reject(DatasetErrEnum.unAuthDatasetCollection); + } + + const isOwner = + String(collection.datasetId.tmbId) === tmbId || role === TeamMemberRoleEnum.owner; + const canWrite = + isOwner || + (role !== TeamMemberRoleEnum.visitor && + collection.datasetId.permission === PermissionTypeEnum.public); + + if (per === 'r') { + if (!isOwner && collection.datasetId.permission !== PermissionTypeEnum.public) { + return Promise.reject(DatasetErrEnum.unAuthDatasetCollection); + } + } + if (per === 'w' && !canWrite) { + return Promise.reject(DatasetErrEnum.unAuthDatasetCollection); + } + if (per === 'owner' && !isOwner) { + return Promise.reject(DatasetErrEnum.unAuthDatasetCollection); + } + + return { + collection, + isOwner, + canWrite + }; + })(); + + return { + userId, + teamId, + tmbId, + collection, + isOwner, + canWrite + }; +} + +export async function authDatasetFile({ + fileId, + per = 'owner', + ...props +}: AuthModeType & { + fileId: string; +}): Promise< + AuthResponseType & { + file: DatasetFileSchema; + } +> { + const { userId, teamId, tmbId } = await parseHeaderCert(props); + const { role } = await getTeamInfoByTmbId({ tmbId }); + + const file = await getFileById({ bucketName: BucketNameEnum.dataset, fileId }); + + if (file.metadata.teamId !== teamId) { + return Promise.reject(DatasetErrEnum.unAuthDatasetFile); + } + + const { dataset } = await authDataset({ + ...props, + datasetId: file.metadata.datasetId, + per + }); + const isOwner = + role !== TeamMemberRoleEnum.visitor && + (String(dataset.tmbId) === tmbId || role === TeamMemberRoleEnum.owner); + + const canWrite = + isOwner || + (role !== TeamMemberRoleEnum.visitor && dataset.permission === PermissionTypeEnum.public); + + if (per === 'r' && !isOwner && dataset.permission !== PermissionTypeEnum.public) { + return Promise.reject(DatasetErrEnum.unAuthDatasetFile); + } + if (per === 'w' && !canWrite) { + return Promise.reject(DatasetErrEnum.unAuthDatasetFile); + } + if (per === 'owner' && !isOwner) { + return Promise.reject(DatasetErrEnum.unAuthDatasetFile); + } + + return { + userId, + teamId, + tmbId, + file, + isOwner, + canWrite + }; +} diff --git a/packages/service/support/permission/auth/openapi.ts b/packages/service/support/permission/auth/openapi.ts new file mode 100644 index 000000000..8dc42418b --- /dev/null +++ b/packages/service/support/permission/auth/openapi.ts @@ -0,0 +1,61 @@ +import { AuthResponseType } from '@fastgpt/global/support/permission/type'; +import { AuthModeType } from '../type'; +import { OpenApiSchema } from '@fastgpt/global/support/openapi/type'; +import { parseHeaderCert } from '../controller'; +import { getTeamInfoByTmbId } from '../../user/team/controller'; +import { MongoOpenApi } from '../../openapi/schema'; +import { OpenApiErrEnum } from '@fastgpt/global/common/error/code/openapi'; +import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; +import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; + +export async function authOpenApiKeyCrud({ + id, + per = 'owner', + ...props +}: AuthModeType & { + id: string; +}): Promise< + AuthResponseType & { + openapi: OpenApiSchema; + } +> { + const result = await parseHeaderCert(props); + const { tmbId, teamId } = result; + + const { role } = await getTeamInfoByTmbId({ tmbId }); + + const { openapi, isOwner, canWrite } = await (async () => { + const openapi = await MongoOpenApi.findOne({ _id: id, teamId }); + + if (!openapi) { + throw new Error(OpenApiErrEnum.unExist); + } + + const isOwner = String(openapi.tmbId) === tmbId || role === TeamMemberRoleEnum.owner; + const canWrite = + isOwner || (String(openapi.tmbId) === tmbId && role !== TeamMemberRoleEnum.visitor); + + if (per === 'r' && !canWrite) { + return Promise.reject(OpenApiErrEnum.unAuth); + } + if (per === 'w' && !canWrite) { + return Promise.reject(OpenApiErrEnum.unAuth); + } + if (per === 'owner' && !isOwner) { + return Promise.reject(OpenApiErrEnum.unAuth); + } + + return { + openapi, + isOwner, + canWrite + }; + })(); + + return { + ...result, + openapi, + isOwner, + canWrite + }; +} diff --git a/packages/service/support/permission/auth/outLink.ts b/packages/service/support/permission/auth/outLink.ts new file mode 100644 index 000000000..4250f03d7 --- /dev/null +++ b/packages/service/support/permission/auth/outLink.ts @@ -0,0 +1,98 @@ +import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; +import { AuthModeType } from '../type'; +import { AuthResponseType } from '@fastgpt/global/support/permission/type'; +import { AppDetailType } from '@fastgpt/global/core/app/type'; +import { OutLinkSchema } from '@fastgpt/global/support/outLink/type'; +import { parseHeaderCert } from '../controller'; +import { MongoOutLink } from '../../outLink/schema'; +import { MongoApp } from '../../../core/app/schema'; +import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink'; +import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { AppErrEnum } from '@fastgpt/global/common/error/code/app'; +import { getTeamInfoByTmbId } from '../../user/team/controller'; + +/* crud outlink permission */ +export async function authOutLinkCrud({ + outLinkId, + per = 'owner', + ...props +}: AuthModeType & { + outLinkId: string; +}): Promise< + AuthResponseType & { + app: AppDetailType; + outLink: OutLinkSchema; + } +> { + const result = await parseHeaderCert(props); + const { tmbId, teamId } = result; + + const { role } = await getTeamInfoByTmbId({ tmbId }); + + const { app, outLink, isOwner, canWrite } = await (async () => { + const outLink = await MongoOutLink.findOne({ _id: outLinkId, teamId }); + + if (!outLink) { + throw new Error(OutLinkErrEnum.unExist); + } + + const app = await MongoApp.findById(outLink.appId); + + if (!app) { + return Promise.reject(AppErrEnum.unExist); + } + + const isOwner = String(outLink.tmbId) === tmbId || role === TeamMemberRoleEnum.owner; + const canWrite = + isOwner || + (app.permission === PermissionTypeEnum.public && role !== TeamMemberRoleEnum.visitor); + + if (per === 'r' && !isOwner && app.permission !== PermissionTypeEnum.public) { + return Promise.reject(OutLinkErrEnum.unAuthLink); + } + if (per === 'w' && !canWrite) { + return Promise.reject(OutLinkErrEnum.unAuthLink); + } + if (per === 'owner' && !isOwner) { + return Promise.reject(OutLinkErrEnum.unAuthLink); + } + + return { + app: { + ...app, + isOwner: String(app.tmbId) === tmbId, + canWrite + }, + outLink, + isOwner, + canWrite + }; + })(); + + return { + ...result, + app, + outLink, + isOwner, + canWrite + }; +} + +export async function authOutLinkValid({ shareId }: { shareId?: string }) { + const shareChat = await MongoOutLink.findOne({ shareId }); + + if (!shareChat) { + return Promise.reject(OutLinkErrEnum.linkUnInvalid); + } + + const app = await MongoApp.findById(shareChat.appId); + + if (!app) { + return Promise.reject(AppErrEnum.unExist); + } + + return { + app, + shareChat + }; +} diff --git a/packages/service/support/permission/auth/plugin.ts b/packages/service/support/permission/auth/plugin.ts new file mode 100644 index 000000000..5053ad4d1 --- /dev/null +++ b/packages/service/support/permission/auth/plugin.ts @@ -0,0 +1,56 @@ +import { AuthResponseType } from '@fastgpt/global/support/permission/type'; +import { AuthModeType } from '../type'; +import { parseHeaderCert } from '../controller'; +import { getTeamInfoByTmbId } from '../../user/team/controller'; +import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; +import { MongoPlugin } from '../../../core/plugin/schema'; +import { PluginErrEnum } from '@fastgpt/global/common/error/code/plugin'; +import { PluginItemSchema } from '@fastgpt/global/core/plugin/type'; + +export async function authPluginCrud({ + id, + per = 'owner', + ...props +}: AuthModeType & { + id: string; +}): Promise< + AuthResponseType & { + plugin: PluginItemSchema; + } +> { + const result = await parseHeaderCert(props); + const { tmbId, teamId } = result; + + const { role } = await getTeamInfoByTmbId({ tmbId }); + + const { plugin, isOwner, canWrite } = await (async () => { + const plugin = await MongoPlugin.findOne({ _id: id, teamId }); + + if (!plugin) { + throw new Error(PluginErrEnum.unExist); + } + + const isOwner = String(plugin.tmbId) === tmbId || role === TeamMemberRoleEnum.owner; + const canWrite = isOwner; + + if (per === 'w' && !canWrite) { + return Promise.reject(PluginErrEnum.unAuth); + } + if (per === 'owner' && !isOwner) { + return Promise.reject(PluginErrEnum.unAuth); + } + + return { + plugin, + isOwner, + canWrite + }; + })(); + + return { + ...result, + plugin, + isOwner, + canWrite + }; +} diff --git a/packages/service/support/permission/auth/user.ts b/packages/service/support/permission/auth/user.ts new file mode 100644 index 000000000..171088dd6 --- /dev/null +++ b/packages/service/support/permission/auth/user.ts @@ -0,0 +1,52 @@ +import { AuthResponseType } from '@fastgpt/global/support/permission/type'; +import { AuthModeType } from '../type'; +import { TeamItemType } from '@fastgpt/global/support/user/team/type'; +import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; +import { parseHeaderCert } from '../controller'; +import { getTeamInfoByTmbId } from '../../user/team/controller'; +import { UserErrEnum } from '../../../../global/common/error/code/user'; + +export async function authUserNotVisitor(props: AuthModeType): Promise< + AuthResponseType & { + team: TeamItemType; + role: `${TeamMemberRoleEnum}`; + } +> { + const { userId, teamId, tmbId } = await parseHeaderCert(props); + const team = await getTeamInfoByTmbId({ tmbId }); + + if (team.role === TeamMemberRoleEnum.visitor) { + return Promise.reject(UserErrEnum.binVisitor); + } + + return { + userId, + teamId, + tmbId, + team, + role: team.role, + isOwner: team.role === TeamMemberRoleEnum.owner, // teamOwner + canWrite: true + }; +} + +/* auth user role */ +export async function authUserRole(props: AuthModeType): Promise< + AuthResponseType & { + role: `${TeamMemberRoleEnum}`; + teamOwner: boolean; + } +> { + const { userId, teamId, tmbId } = await parseHeaderCert(props); + const { role: userRole, canWrite } = await getTeamInfoByTmbId({ tmbId }); + + return { + userId, + teamId, + tmbId, + isOwner: true, + role: userRole, + teamOwner: userRole === TeamMemberRoleEnum.owner, + canWrite + }; +} diff --git a/packages/service/support/permission/controller.ts b/packages/service/support/permission/controller.ts new file mode 100644 index 000000000..1e92419d1 --- /dev/null +++ b/packages/service/support/permission/controller.ts @@ -0,0 +1,241 @@ +import Cookie from 'cookie'; +import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode'; +import jwt from 'jsonwebtoken'; +import { NextApiResponse } from 'next'; +import type { AuthModeType, ReqHeaderAuthType } from './type.d'; +import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { authOpenApiKey } from '../openapi/auth'; +import { FileTokenQuery } from '@fastgpt/global/common/file/type'; + +/* create token */ +export function createJWT(user: { _id?: string; team?: { teamId?: string; tmbId: string } }) { + const key = process.env.TOKEN_KEY as string; + const token = jwt.sign( + { + userId: String(user._id), + teamId: String(user.team?.teamId), + tmbId: String(user.team?.tmbId), + exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7 + }, + key + ); + return token; +} + +// auth token +export function authJWT(token: string) { + return new Promise<{ + userId: string; + teamId: string; + tmbId: string; + }>((resolve, reject) => { + const key = process.env.TOKEN_KEY as string; + + jwt.verify(token, key, function (err, decoded: any) { + if (err || !decoded?.userId) { + reject(ERROR_ENUM.unAuthorization); + return; + } + + resolve({ + userId: decoded.userId, + teamId: decoded.teamId || '', + tmbId: decoded.tmbId + }); + }); + }); +} + +export async function parseHeaderCert({ + req, + authToken = false, + authRoot = false, + authApiKey = false +}: AuthModeType) { + // parse jwt + async function authCookieToken(cookie?: string, token?: string) { + // 获取 cookie + const cookies = Cookie.parse(cookie || ''); + const cookieToken = cookies.token || token; + + if (!cookieToken) { + return Promise.reject(ERROR_ENUM.unAuthorization); + } + + return await authJWT(cookieToken); + } + // from authorization get apikey + async function parseAuthorization(authorization?: string) { + if (!authorization) { + return Promise.reject(ERROR_ENUM.unAuthorization); + } + + // Bearer fastgpt-xxxx-appId + const auth = authorization.split(' ')[1]; + if (!auth) { + return Promise.reject(ERROR_ENUM.unAuthorization); + } + + const { apikey, appId: authorizationAppid = '' } = await (async () => { + const arr = auth.split('-'); + // abandon + if (arr.length === 3) { + return { + apikey: `${arr[0]}-${arr[1]}`, + appId: arr[2] + }; + } + if (arr.length === 2) { + return { + apikey: auth + }; + } + return Promise.reject(ERROR_ENUM.unAuthorization); + })(); + + // auth apikey + const { userId, teamId, tmbId, appId: apiKeyAppId = '' } = await authOpenApiKey({ apikey }); + + return { + uid: userId, + teamId, + tmbId, + apikey, + appId: apiKeyAppId || authorizationAppid + }; + } + // root user + async function parseRootKey(rootKey?: string, userId = '') { + if (!rootKey || !process.env.ROOT_KEY || rootKey !== process.env.ROOT_KEY) { + return Promise.reject(ERROR_ENUM.unAuthorization); + } + return userId; + } + + const { cookie, token, apikey, rootkey, userid, authorization } = (req.headers || + {}) as ReqHeaderAuthType; + + const { uid, teamId, tmbId, appId, openApiKey, authType } = await (async () => { + if (authToken && (cookie || token)) { + // user token(from fastgpt web) + const res = await authCookieToken(cookie, token); + return { + uid: res.userId, + teamId: res.teamId, + tmbId: res.tmbId, + appId: '', + openApiKey: '', + authType: AuthUserTypeEnum.token + }; + } + if (authRoot && rootkey) { + // root user + return { + uid: await parseRootKey(rootkey, userid), + teamId: '', + tmbId: '', + appId: '', + openApiKey: '', + authType: AuthUserTypeEnum.root + }; + } + if (authApiKey && apikey) { + // apikey + const parseResult = await authOpenApiKey({ apikey }); + return { + uid: parseResult.userId, + teamId: parseResult.teamId, + tmbId: parseResult.tmbId, + appId: parseResult.appId, + openApiKey: parseResult.apikey, + authType: AuthUserTypeEnum.apikey + }; + } + + if (authApiKey && authorization) { + // apikey from authorization + const authResponse = await parseAuthorization(authorization); + return { + uid: authResponse.uid, + teamId: authResponse.teamId, + tmbId: authResponse.tmbId, + appId: authResponse.appId, + openApiKey: authResponse.apikey, + authType: AuthUserTypeEnum.apikey + }; + } + return { + uid: '', + teamId: '', + tmbId: '', + appId: '', + openApiKey: '', + authType: AuthUserTypeEnum.token + }; + })(); + + // not rootUser and no uid, reject request + if (!rootkey && !uid && !teamId && !tmbId) { + return Promise.reject(ERROR_ENUM.unAuthorization); + } + + return { + userId: String(uid), + teamId: String(teamId), + tmbId: String(tmbId), + appId, + authType, + apikey: openApiKey + }; +} + +/* set cookie */ +export const setCookie = (res: NextApiResponse, token: string) => { + res.setHeader( + 'Set-Cookie', + `token=${token}; Path=/; HttpOnly; Max-Age=604800; Samesite=None; Secure;` + ); +}; +/* clear cookie */ +export const clearCookie = (res: NextApiResponse) => { + res.setHeader('Set-Cookie', 'token=; Path=/; Max-Age=0'); +}; + +/* file permission */ +export const createFileToken = (data: FileTokenQuery) => { + if (!process.env.FILE_TOKEN_KEY) { + return Promise.reject('System unset FILE_TOKEN_KEY'); + } + const expiredTime = Math.floor(Date.now() / 1000) + 60 * 30; + + const key = process.env.FILE_TOKEN_KEY as string; + const token = jwt.sign( + { + ...data, + exp: expiredTime + }, + key + ); + return Promise.resolve(token); +}; + +export const authFileToken = (token?: string) => + new Promise((resolve, reject) => { + if (!token) { + return reject(ERROR_ENUM.unAuthFile); + } + const key = process.env.FILE_TOKEN_KEY as string; + + jwt.verify(token, key, function (err, decoded: any) { + if (err || !decoded.bucketName || !decoded?.teamId || !decoded?.tmbId || !decoded?.fileId) { + reject(ERROR_ENUM.unAuthFile); + return; + } + resolve({ + bucketName: decoded.bucketName, + teamId: decoded.teamId, + tmbId: decoded.tmbId, + fileId: decoded.fileId + }); + }); + }); diff --git a/packages/service/support/permission/type.d.ts b/packages/service/support/permission/type.d.ts new file mode 100644 index 000000000..811804122 --- /dev/null +++ b/packages/service/support/permission/type.d.ts @@ -0,0 +1,17 @@ +import { NextApiRequest } from 'next'; + +export type ReqHeaderAuthType = { + cookie?: string; + token?: string; + apikey?: string; // abandon + rootkey?: string; + userid?: string; + authorization?: string; +}; +export type AuthModeType = { + req: NextApiRequest; + authToken?: boolean; + authRoot?: boolean; + authApiKey?: boolean; + per?: 'r' | 'w' | 'owner'; +}; diff --git a/packages/service/support/user/auth.ts b/packages/service/support/user/auth.ts deleted file mode 100644 index 98bdf5dc7..000000000 --- a/packages/service/support/user/auth.ts +++ /dev/null @@ -1,206 +0,0 @@ -import type { NextApiResponse, NextApiRequest } from 'next'; -import Cookie from 'cookie'; -import jwt from 'jsonwebtoken'; -import { authOpenApiKey } from '../openapi/auth'; -import { authOutLinkId } from '../outLink/auth'; -import { MongoUser } from './schema'; -import type { UserModelSchema } from '@fastgpt/global/support/user/type'; -import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode'; - -export enum AuthUserTypeEnum { - token = 'token', - root = 'root', - apikey = 'apikey', - outLink = 'outLink' -} - -/* auth balance */ -export const authBalanceByUid = async (uid: string) => { - const user = await MongoUser.findById( - uid, - '_id username balance openaiAccount timezone' - ); - if (!user) { - return Promise.reject(ERROR_ENUM.unAuthorization); - } - - if (user.balance <= 0) { - return Promise.reject(ERROR_ENUM.insufficientQuota); - } - return user; -}; - -/* uniform auth user */ -export const authUser = async ({ - req, - authToken = false, - authRoot = false, - authApiKey = false, - authBalance = false, - authOutLink -}: { - req: NextApiRequest; - authToken?: boolean; - authRoot?: boolean; - authApiKey?: boolean; - authBalance?: boolean; - authOutLink?: boolean; -}) => { - const authCookieToken = async (cookie?: string, token?: string): Promise => { - // 获取 cookie - const cookies = Cookie.parse(cookie || ''); - const cookieToken = cookies.token || token; - - if (!cookieToken) { - return Promise.reject(ERROR_ENUM.unAuthorization); - } - - return await authJWT(cookieToken); - }; - // from authorization get apikey - const parseAuthorization = async (authorization?: string) => { - if (!authorization) { - return Promise.reject(ERROR_ENUM.unAuthorization); - } - - // Bearer fastgpt-xxxx-appId - const auth = authorization.split(' ')[1]; - if (!auth) { - return Promise.reject(ERROR_ENUM.unAuthorization); - } - - const { apikey, appId: authorizationAppid = '' } = await (async () => { - const arr = auth.split('-'); - // abandon - if (arr.length === 3) { - return { - apikey: `${arr[0]}-${arr[1]}`, - appId: arr[2] - }; - } - if (arr.length === 2) { - return { - apikey: auth - }; - } - return Promise.reject(ERROR_ENUM.unAuthorization); - })(); - - // auth apikey - const { userId, appId: apiKeyAppId = '' } = await authOpenApiKey({ apikey }); - - return { - uid: userId, - apikey, - appId: apiKeyAppId || authorizationAppid - }; - }; - // root user - const parseRootKey = async (rootKey?: string, userId = '') => { - if (!rootKey || !process.env.ROOT_KEY || rootKey !== process.env.ROOT_KEY) { - return Promise.reject(ERROR_ENUM.unAuthorization); - } - return userId; - }; - - const { cookie, token, apikey, rootkey, userid, authorization } = (req.headers || {}) as { - cookie?: string; - token?: string; - apikey?: string; - rootkey?: string; // abandon - userid?: string; - authorization?: string; - }; - const { shareId } = (req?.body || {}) as { shareId?: string }; - - let uid = ''; - let appId = ''; - let openApiKey = apikey; - let authType: `${AuthUserTypeEnum}` = AuthUserTypeEnum.token; - - if (authOutLink && shareId) { - const res = await authOutLinkId({ id: shareId }); - uid = res.userId; - authType = AuthUserTypeEnum.outLink; - } else if (authToken && (cookie || token)) { - // user token(from fastgpt web) - uid = await authCookieToken(cookie, token); - authType = AuthUserTypeEnum.token; - } else if (authRoot && rootkey) { - // root user - uid = await parseRootKey(rootkey, userid); - authType = AuthUserTypeEnum.root; - } else if (authApiKey && apikey) { - // apikey - const parseResult = await authOpenApiKey({ apikey }); - uid = parseResult.userId; - authType = AuthUserTypeEnum.apikey; - openApiKey = parseResult.apikey; - } else if (authApiKey && authorization) { - // apikey from authorization - const authResponse = await parseAuthorization(authorization); - uid = authResponse.uid; - appId = authResponse.appId; - openApiKey = authResponse.apikey; - authType = AuthUserTypeEnum.apikey; - } - - // not rootUser and no uid, reject request - if (!rootkey && !uid) { - return Promise.reject(ERROR_ENUM.unAuthorization); - } - - // balance check - const user = await (() => { - if (authBalance) { - return authBalanceByUid(uid); - } - })(); - - return { - userId: String(uid), - appId, - authType, - user, - apikey: openApiKey - }; -}; - -/* 生成 token */ -export function generateToken(userId: string) { - const key = process.env.TOKEN_KEY as string; - const token = jwt.sign( - { - userId, - exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7 - }, - key - ); - return token; -} -// auth token -export function authJWT(token: string) { - return new Promise((resolve, reject) => { - const key = process.env.TOKEN_KEY as string; - - jwt.verify(token, key, function (err, decoded: any) { - if (err || !decoded?.userId) { - reject(ERROR_ENUM.unAuthorization); - return; - } - resolve(decoded.userId); - }); - }); -} - -/* set cookie */ -export const setCookie = (res: NextApiResponse, token: string) => { - res.setHeader( - 'Set-Cookie', - `token=${token}; Path=/; HttpOnly; Max-Age=604800; Samesite=None; Secure;` - ); -}; -/* clear cookie */ -export const clearCookie = (res: NextApiResponse) => { - res.setHeader('Set-Cookie', 'token=; Path=/; Max-Age=0'); -}; diff --git a/packages/service/support/user/controller.ts b/packages/service/support/user/controller.ts new file mode 100644 index 000000000..5e4720604 --- /dev/null +++ b/packages/service/support/user/controller.ts @@ -0,0 +1,11 @@ +import { MongoUser } from './schema'; + +export async function authUserExist({ userId, username }: { userId?: string; username?: string }) { + if (userId) { + return MongoUser.findOne({ _id: userId }); + } + if (username) { + return MongoUser.findOne({ username }); + } + return null; +} diff --git a/packages/service/support/user/inform/controller.ts b/packages/service/support/user/inform/controller.ts deleted file mode 100644 index d8c0057ef..000000000 --- a/packages/service/support/user/inform/controller.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { MongoUserInform } from './schema'; -import { MongoUser } from '../schema'; -import { InformTypeEnum } from '@fastgpt/global/support/user/constant'; - -export type SendInformProps = { - type: `${InformTypeEnum}`; - title: string; - content: string; -}; - -export async function sendInform2AllUser({ type, title, content }: SendInformProps) { - const users = await MongoUser.find({}, '_id'); - await MongoUserInform.insertMany( - users.map(({ _id }) => ({ - type, - title, - content, - userId: _id - })) - ); -} - -export async function sendInform2OneUser({ - type, - title, - content, - userId -}: SendInformProps & { userId: string }) { - const inform = await MongoUserInform.findOne({ - type, - title, - content, - userId, - time: { $gte: new Date(Date.now() - 5 * 60 * 1000) } - }); - - if (inform) return; - - await MongoUserInform.create({ - type, - title, - content, - userId - }); -} diff --git a/packages/service/support/user/inform/schema.ts b/packages/service/support/user/inform/schema.ts deleted file mode 100644 index fc47154fe..000000000 --- a/packages/service/support/user/inform/schema.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { connectionMongo, type Model } from '../../../common/mongo'; -const { Schema, model, models } = connectionMongo; -import type { UserInformSchema } from '@fastgpt/global/support/user/type.d'; -import { InformTypeMap } from '@fastgpt/global/support/user/constant'; - -const InformSchema = new Schema({ - userId: { - type: Schema.Types.ObjectId, - ref: 'user', - required: true - }, - time: { - type: Date, - default: () => new Date() - }, - type: { - type: String, - enum: Object.keys(InformTypeMap) - }, - title: { - type: String, - required: true - }, - content: { - type: String, - required: true - }, - read: { - type: Boolean, - default: false - } -}); - -try { - InformSchema.index({ time: -1 }); - InformSchema.index({ userId: 1 }); -} catch (error) { - console.log(error); -} - -export const MongoUserInform: Model = - models['inform'] || model('inform', InformSchema); diff --git a/packages/service/support/user/schema.ts b/packages/service/support/user/schema.ts index 29c397d45..995ff0953 100644 --- a/packages/service/support/user/schema.ts +++ b/packages/service/support/user/schema.ts @@ -1,7 +1,7 @@ import { connectionMongo, type Model } from '../../common/mongo'; const { Schema, model, models } = connectionMongo; import { hashStr } from '@fastgpt/global/common/string/tools'; -import { PRICE_SCALE } from '@fastgpt/global/common/bill/constants'; +import { PRICE_SCALE } from '@fastgpt/global/support/wallet/bill/constants'; import type { UserModelSchema } from '@fastgpt/global/support/user/type'; export const userCollectionName = 'users'; diff --git a/packages/service/support/user/team/controller.ts b/packages/service/support/user/team/controller.ts new file mode 100644 index 000000000..6ae909eaf --- /dev/null +++ b/packages/service/support/user/team/controller.ts @@ -0,0 +1,125 @@ +import { TeamItemType } from '@fastgpt/global/support/user/team/type'; +import { connectionMongo, Types } from '../../../common/mongo'; +import { + TeamMemberRoleEnum, + TeamMemberStatusEnum, + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; + +export async function getTeamInfoByTmbId({ + tmbId, + userId +}: { + tmbId?: string; + userId?: string; +}): Promise { + if (!tmbId && !userId) { + return Promise.reject('tmbId or userId is required'); + } + + const db = connectionMongo?.connection?.db; + + const TeamMember = db.collection(TeamMemberCollectionName); + + const results = await TeamMember.aggregate([ + { + $match: tmbId + ? { + _id: new Types.ObjectId(tmbId) + } + : { + userId: new Types.ObjectId(userId), + defaultTeam: true + } + }, + { + $lookup: { + from: TeamCollectionName, // 关联的集合名 + localField: 'teamId', // TeamMember 集合中用于关联的字段 + foreignField: '_id', // Team 集合中用于关联的字段 + as: 'team' // 查询结果中的字段名,存放关联查询的结果 + } + }, + { + $unwind: '$team' // 将查询结果中的 team 字段展开,变成一个对象 + } + ]).toArray(); + const tmb = results[0]; + + if (!tmb) { + return Promise.reject('team not exist'); + } + + return { + userId: String(tmb.userId), + teamId: String(tmb.teamId), + teamName: tmb.team.name, + avatar: tmb.team.avatar, + balance: tmb.team.balance, + tmbId: String(tmb._id), + role: tmb.role, + status: tmb.status, + defaultTeam: tmb.defaultTeam, + canWrite: tmb.role !== TeamMemberRoleEnum.visitor, + maxSize: tmb.team.maxSize + }; +} +export async function createDefaultTeam({ + userId, + teamName = 'My Team', + avatar = '/icon/logo.svg', + balance = 0, + maxSize = 5 +}: { + userId: string; + teamName?: string; + avatar?: string; + balance?: number; + maxSize?: number; +}) { + const db = connectionMongo.connection.db; + const Team = db.collection(TeamCollectionName); + const TeamMember = db.collection(TeamMemberCollectionName); + + // auth default team + const tmb = await TeamMember.findOne({ + userId: new Types.ObjectId(userId), + defaultTeam: true + }); + + if (!tmb) { + console.log('create default team', userId); + + // create + const { insertedId } = await Team.insertOne({ + ownerId: userId, + name: teamName, + avatar, + balance, + maxSize, + createTime: new Date() + }); + await TeamMember.insertOne({ + teamId: insertedId, + userId, + role: TeamMemberRoleEnum.owner, + status: TeamMemberStatusEnum.active, + createTime: new Date(), + defaultTeam: true + }); + } else { + console.log('default team exist', userId); + await Team.updateOne( + { + _id: new Types.ObjectId(tmb.teamId) + }, + { + $set: { + balance, + maxSize + } + } + ); + } +} diff --git a/projects/app/src/service/common/bill/schema.ts b/packages/service/support/wallet/bill/schema.ts similarity index 53% rename from projects/app/src/service/common/bill/schema.ts rename to packages/service/support/wallet/bill/schema.ts index 9fb81defc..8673af155 100644 --- a/projects/app/src/service/common/bill/schema.ts +++ b/packages/service/support/wallet/bill/schema.ts @@ -1,12 +1,25 @@ -import { connectionMongo, type Model } from '@fastgpt/service/common/mongo'; +import { connectionMongo, type Model } from '../../../common/mongo'; const { Schema, model, models } = connectionMongo; -import { BillSchema as BillType } from '@/types/common/bill'; -import { BillSourceMap } from '@/constants/user'; +import { BillSchema as BillType } from '@fastgpt/global/support/wallet/bill/type'; +import { BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants'; +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; const BillSchema = new Schema({ userId: { type: Schema.Types.ObjectId, - ref: 'user', + ref: 'user' + }, + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, required: true }, appName: { @@ -44,4 +57,4 @@ try { console.log(error); } -export const Bill: Model = models['bill'] || model('bill', BillSchema); +export const MongoBill: Model = models['bill'] || model('bill', BillSchema); diff --git a/packages/service/support/wallet/pay/schema.ts b/packages/service/support/wallet/pay/schema.ts deleted file mode 100644 index 32a863cc7..000000000 --- a/packages/service/support/wallet/pay/schema.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { connectionMongo, type Model } from '../../../common/mongo'; -const { Schema, model, models } = connectionMongo; -import { PaySchema as PayType } from '@fastgpt/global/support/wallet/type.d'; - -const PaySchema = new Schema({ - userId: { - type: Schema.Types.ObjectId, - ref: 'user', - required: true - }, - createTime: { - type: Date, - default: () => new Date() - }, - price: { - type: Number, - required: true - }, - orderId: { - type: String, - required: true - }, - status: { - // 支付的状态 - type: String, - default: 'NOTPAY', - enum: ['SUCCESS', 'REFUND', 'NOTPAY', 'CLOSED'] - } -}); - -export const MongoPay: Model = models['pay'] || model('pay', PaySchema); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f3a08661..6b6a1d697 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,10 @@ settings: importers: .: + dependencies: + openai: + specifier: 4.16.1 + version: registry.npmmirror.com/openai@4.16.1(encoding@0.1.13) devDependencies: husky: specifier: ^8.0.3 @@ -26,6 +30,9 @@ importers: react-i18next: specifier: ^12.3.1 version: registry.npmmirror.com/react-i18next@12.3.1(i18next@22.5.1)(react-dom@18.2.0)(react@18.2.0) + zhlint: + specifier: ^0.7.1 + version: registry.npmmirror.com/zhlint@0.7.1 packages/global: dependencies: @@ -39,8 +46,8 @@ importers: specifier: ^0.1.13 version: registry.npmmirror.com/encoding@0.1.13 openai: - specifier: ^4.12.1 - version: registry.npmmirror.com/openai@4.12.4(encoding@0.1.13) + specifier: ^4.16.1 + version: registry.npmmirror.com/openai@4.16.1(encoding@0.1.13) timezones-list: specifier: ^3.0.2 version: registry.npmmirror.com/timezones-list@3.0.2 @@ -69,15 +76,18 @@ importers: mongoose: specifier: ^7.0.2 version: registry.npmmirror.com/mongoose@7.6.3 + nanoid: + specifier: ^4.0.1 + version: registry.npmmirror.com/nanoid@4.0.2 next: specifier: 13.5.2 version: registry.npmmirror.com/next@13.5.2(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0)(sass@1.69.4) nextjs-cors: specifier: ^2.1.2 version: registry.npmmirror.com/nextjs-cors@2.1.2(next@13.5.2) - openai: - specifier: ^4.12.4 - version: registry.npmmirror.com/openai@4.12.4(encoding@0.1.13) + pg: + specifier: ^8.10.0 + version: registry.npmmirror.com/pg@8.11.3 tunnel: specifier: ^0.0.6 version: registry.npmmirror.com/tunnel@0.0.6 @@ -97,6 +107,9 @@ importers: '@types/node': specifier: ^20.8.5 version: registry.npmmirror.com/@types/node@20.8.7 + '@types/pg': + specifier: ^8.6.6 + version: registry.npmmirror.com/@types/pg@8.10.7 '@types/tunnel': specifier: ^0.0.4 version: registry.npmmirror.com/@types/tunnel@0.0.4 @@ -223,9 +236,6 @@ importers: papaparse: specifier: ^5.4.1 version: registry.npmmirror.com/papaparse@5.4.1 - pg: - specifier: ^8.10.0 - version: registry.npmmirror.com/pg@8.11.3 pg-query-stream: specifier: ^4.5.3 version: registry.npmmirror.com/pg-query-stream@4.5.3(pg@8.11.3) @@ -305,9 +315,6 @@ importers: '@types/papaparse': specifier: ^5.3.7 version: registry.npmmirror.com/@types/papaparse@5.3.10 - '@types/pg': - specifier: ^8.6.6 - version: registry.npmmirror.com/@types/pg@8.10.7 '@types/react': specifier: 18.0.28 version: registry.npmmirror.com/@types/react@18.0.28 @@ -3453,6 +3460,28 @@ packages: version: 0.3.1 dev: false + registry.npmmirror.com/@esbuild/android-arm@0.15.18: + resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.15.18.tgz} + name: '@esbuild/android-arm' + version: 0.15.18 + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/@esbuild/linux-loong64@0.15.18: + resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz} + name: '@esbuild/linux-loong64' + version: 0.15.18 + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + registry.npmmirror.com/@eslint/eslintrc@1.4.1: resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz} name: '@eslint/eslintrc' @@ -4071,6 +4100,20 @@ packages: '@types/node': registry.npmmirror.com/@types/node@20.8.7 dev: true + registry.npmmirror.com/@types/chai-subset@1.3.4: + resolution: {integrity: sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/chai-subset/-/chai-subset-1.3.4.tgz} + name: '@types/chai-subset' + version: 1.3.4 + dependencies: + '@types/chai': registry.npmmirror.com/@types/chai@4.3.9 + dev: true + + registry.npmmirror.com/@types/chai@4.3.9: + resolution: {integrity: sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/chai/-/chai-4.3.9.tgz} + name: '@types/chai' + version: 4.3.9 + dev: true + registry.npmmirror.com/@types/connect@3.4.37: resolution: {integrity: sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/connect/-/connect-3.4.37.tgz} name: '@types/connect' @@ -4636,7 +4679,6 @@ packages: resolution: {integrity: sha512-zC0iXxAv1C1ERURduJueYzkzZ2zaGyc+P2c95hgkikHPr3z8EdUZOlgEQ5X0DRmwDZn+hekycQnoeiiRVrmilQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/unist/-/unist-2.0.9.tgz} name: '@types/unist' version: 2.0.9 - dev: false registry.npmmirror.com/@types/webidl-conversions@7.0.2: resolution: {integrity: sha512-uNv6b/uGRLlCVmelat2rA8bcVd3k/42mV2EmjhPh6JLkd35T5bgwR/t6xy7a9MWhd9sixIeBUzhBenvk3NO+DQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/webidl-conversions/-/webidl-conversions-7.0.2.tgz} @@ -5011,6 +5053,35 @@ packages: version: 2.0.6 dev: false + registry.npmmirror.com/asn1.js@5.4.1: + resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/asn1.js/-/asn1.js-5.4.1.tgz} + name: asn1.js + version: 5.4.1 + dependencies: + bn.js: registry.npmmirror.com/bn.js@4.12.0 + inherits: registry.npmmirror.com/inherits@2.0.4 + minimalistic-assert: registry.npmmirror.com/minimalistic-assert@1.0.1 + safer-buffer: registry.npmmirror.com/safer-buffer@2.1.2 + dev: true + + registry.npmmirror.com/assert@2.1.0: + resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/assert/-/assert-2.1.0.tgz} + name: assert + version: 2.1.0 + dependencies: + call-bind: registry.npmmirror.com/call-bind@1.0.5 + is-nan: registry.npmmirror.com/is-nan@1.3.2 + object-is: registry.npmmirror.com/object-is@1.1.5 + object.assign: registry.npmmirror.com/object.assign@4.1.4 + util: registry.npmmirror.com/util@0.12.5 + dev: true + + registry.npmmirror.com/assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/assertion-error/-/assertion-error-1.1.0.tgz} + name: assertion-error + version: 1.1.0 + dev: true + registry.npmmirror.com/ast-types-flow@0.0.7: resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz} name: ast-types-flow @@ -5127,6 +5198,12 @@ packages: - supports-color dev: true + registry.npmmirror.com/bail@1.0.5: + resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/bail/-/bail-1.0.5.tgz} + name: bail + version: 1.0.5 + dev: true + registry.npmmirror.com/bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/bail/-/bail-2.0.2.tgz} name: bail @@ -5149,7 +5226,6 @@ packages: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz} name: base64-js version: 1.5.1 - dev: false registry.npmmirror.com/binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz} @@ -5172,6 +5248,18 @@ packages: version: 3.4.7 dev: false + registry.npmmirror.com/bn.js@4.12.0: + resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz} + name: bn.js + version: 4.12.0 + dev: true + + registry.npmmirror.com/bn.js@5.2.1: + resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/bn.js/-/bn.js-5.2.1.tgz} + name: bn.js + version: 5.2.1 + dev: true + registry.npmmirror.com/boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz} name: boolbase @@ -5195,6 +5283,88 @@ packages: dependencies: fill-range: registry.npmmirror.com/fill-range@7.0.1 + registry.npmmirror.com/brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/brorand/-/brorand-1.1.0.tgz} + name: brorand + version: 1.1.0 + dev: true + + registry.npmmirror.com/browser-resolve@2.0.0: + resolution: {integrity: sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/browser-resolve/-/browser-resolve-2.0.0.tgz} + name: browser-resolve + version: 2.0.0 + dependencies: + resolve: registry.npmmirror.com/resolve@1.22.8 + dev: true + + registry.npmmirror.com/browserify-aes@1.2.0: + resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/browserify-aes/-/browserify-aes-1.2.0.tgz} + name: browserify-aes + version: 1.2.0 + dependencies: + buffer-xor: registry.npmmirror.com/buffer-xor@1.0.3 + cipher-base: registry.npmmirror.com/cipher-base@1.0.4 + create-hash: registry.npmmirror.com/create-hash@1.2.0 + evp_bytestokey: registry.npmmirror.com/evp_bytestokey@1.0.3 + inherits: registry.npmmirror.com/inherits@2.0.4 + safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 + dev: true + + registry.npmmirror.com/browserify-cipher@1.0.1: + resolution: {integrity: sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz} + name: browserify-cipher + version: 1.0.1 + dependencies: + browserify-aes: registry.npmmirror.com/browserify-aes@1.2.0 + browserify-des: registry.npmmirror.com/browserify-des@1.0.2 + evp_bytestokey: registry.npmmirror.com/evp_bytestokey@1.0.3 + dev: true + + registry.npmmirror.com/browserify-des@1.0.2: + resolution: {integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/browserify-des/-/browserify-des-1.0.2.tgz} + name: browserify-des + version: 1.0.2 + dependencies: + cipher-base: registry.npmmirror.com/cipher-base@1.0.4 + des.js: registry.npmmirror.com/des.js@1.1.0 + inherits: registry.npmmirror.com/inherits@2.0.4 + safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 + dev: true + + registry.npmmirror.com/browserify-rsa@4.1.0: + resolution: {integrity: sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz} + name: browserify-rsa + version: 4.1.0 + dependencies: + bn.js: registry.npmmirror.com/bn.js@5.2.1 + randombytes: registry.npmmirror.com/randombytes@2.1.0 + dev: true + + registry.npmmirror.com/browserify-sign@4.2.2: + resolution: {integrity: sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/browserify-sign/-/browserify-sign-4.2.2.tgz} + name: browserify-sign + version: 4.2.2 + engines: {node: '>= 4'} + dependencies: + bn.js: registry.npmmirror.com/bn.js@5.2.1 + browserify-rsa: registry.npmmirror.com/browserify-rsa@4.1.0 + create-hash: registry.npmmirror.com/create-hash@1.2.0 + create-hmac: registry.npmmirror.com/create-hmac@1.1.7 + elliptic: registry.npmmirror.com/elliptic@6.5.4 + inherits: registry.npmmirror.com/inherits@2.0.4 + parse-asn1: registry.npmmirror.com/parse-asn1@5.1.6 + readable-stream: registry.npmmirror.com/readable-stream@3.6.2 + safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 + dev: true + + registry.npmmirror.com/browserify-zlib@0.2.0: + resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz} + name: browserify-zlib + version: 0.2.0 + dependencies: + pako: registry.npmmirror.com/pako@1.0.11 + dev: true + registry.npmmirror.com/browserslist@4.22.1: resolution: {integrity: sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/browserslist/-/browserslist-4.22.1.tgz} name: browserslist @@ -5240,6 +5410,27 @@ packages: engines: {node: '>=4'} dev: false + registry.npmmirror.com/buffer-xor@1.0.3: + resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/buffer-xor/-/buffer-xor-1.0.3.tgz} + name: buffer-xor + version: 1.0.3 + dev: true + + registry.npmmirror.com/buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz} + name: buffer + version: 5.7.1 + dependencies: + base64-js: registry.npmmirror.com/base64-js@1.5.1 + ieee754: registry.npmmirror.com/ieee754@1.2.1 + dev: true + + registry.npmmirror.com/builtin-status-codes@3.0.0: + resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz} + name: builtin-status-codes + version: 3.0.0 + dev: true + registry.npmmirror.com/busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/busboy/-/busboy-1.6.0.tgz} name: busboy @@ -5281,6 +5472,21 @@ packages: version: 2.0.1 dev: false + registry.npmmirror.com/chai@4.3.10: + resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/chai/-/chai-4.3.10.tgz} + name: chai + version: 4.3.10 + engines: {node: '>=4'} + dependencies: + assertion-error: registry.npmmirror.com/assertion-error@1.1.0 + check-error: registry.npmmirror.com/check-error@1.0.3 + deep-eql: registry.npmmirror.com/deep-eql@4.1.3 + get-func-name: registry.npmmirror.com/get-func-name@2.0.2 + loupe: registry.npmmirror.com/loupe@2.3.7 + pathval: registry.npmmirror.com/pathval@1.1.1 + type-detect: registry.npmmirror.com/type-detect@4.0.8 + dev: true + registry.npmmirror.com/chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz} name: chalk @@ -5312,13 +5518,11 @@ packages: resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz} name: character-entities-legacy version: 1.1.4 - dev: false registry.npmmirror.com/character-entities@1.2.4: resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/character-entities/-/character-entities-1.2.4.tgz} name: character-entities version: 1.2.4 - dev: false registry.npmmirror.com/character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/character-entities/-/character-entities-2.0.2.tgz} @@ -5330,7 +5534,6 @@ packages: resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz} name: character-reference-invalid version: 1.1.4 - dev: false registry.npmmirror.com/charenc@0.0.2: resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/charenc/-/charenc-0.0.2.tgz} @@ -5338,6 +5541,14 @@ packages: version: 0.0.2 dev: false + registry.npmmirror.com/check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/check-error/-/check-error-1.0.3.tgz} + name: check-error + version: 1.0.3 + dependencies: + get-func-name: registry.npmmirror.com/get-func-name@2.0.2 + dev: true + registry.npmmirror.com/chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz} name: chokidar @@ -5354,6 +5565,15 @@ packages: optionalDependencies: fsevents: registry.npmmirror.com/fsevents@2.3.3 + registry.npmmirror.com/cipher-base@1.0.4: + resolution: {integrity: sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cipher-base/-/cipher-base-1.0.4.tgz} + name: cipher-base + version: 1.0.4 + dependencies: + inherits: registry.npmmirror.com/inherits@2.0.4 + safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 + dev: true + registry.npmmirror.com/classcat@5.0.4: resolution: {integrity: sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/classcat/-/classcat-5.0.4.tgz} name: classcat @@ -5390,6 +5610,12 @@ packages: name: client-only version: 0.0.1 + registry.npmmirror.com/collapse-white-space@1.0.6: + resolution: {integrity: sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz} + name: collapse-white-space + version: 1.0.6 + dev: true + registry.npmmirror.com/color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz} name: color-convert @@ -5520,6 +5746,18 @@ packages: typedarray: registry.npmmirror.com/typedarray@0.0.6 dev: false + registry.npmmirror.com/console-browserify@1.2.0: + resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/console-browserify/-/console-browserify-1.2.0.tgz} + name: console-browserify + version: 1.2.0 + dev: true + + registry.npmmirror.com/constants-browserify@1.0.0: + resolution: {integrity: sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/constants-browserify/-/constants-browserify-1.0.0.tgz} + name: constants-browserify + version: 1.0.0 + dev: true + registry.npmmirror.com/convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz} name: convert-source-map @@ -5604,6 +5842,46 @@ packages: path-type: registry.npmmirror.com/path-type@4.0.0 yaml: registry.npmmirror.com/yaml@1.10.2 + registry.npmmirror.com/create-ecdh@4.0.4: + resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/create-ecdh/-/create-ecdh-4.0.4.tgz} + name: create-ecdh + version: 4.0.4 + dependencies: + bn.js: registry.npmmirror.com/bn.js@4.12.0 + elliptic: registry.npmmirror.com/elliptic@6.5.4 + dev: true + + registry.npmmirror.com/create-hash@1.2.0: + resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/create-hash/-/create-hash-1.2.0.tgz} + name: create-hash + version: 1.2.0 + dependencies: + cipher-base: registry.npmmirror.com/cipher-base@1.0.4 + inherits: registry.npmmirror.com/inherits@2.0.4 + md5.js: registry.npmmirror.com/md5.js@1.3.5 + ripemd160: registry.npmmirror.com/ripemd160@2.0.2 + sha.js: registry.npmmirror.com/sha.js@2.4.11 + dev: true + + registry.npmmirror.com/create-hmac@1.1.7: + resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/create-hmac/-/create-hmac-1.1.7.tgz} + name: create-hmac + version: 1.1.7 + dependencies: + cipher-base: registry.npmmirror.com/cipher-base@1.0.4 + create-hash: registry.npmmirror.com/create-hash@1.2.0 + inherits: registry.npmmirror.com/inherits@2.0.4 + ripemd160: registry.npmmirror.com/ripemd160@2.0.2 + safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 + sha.js: registry.npmmirror.com/sha.js@2.4.11 + dev: true + + registry.npmmirror.com/create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz} + name: create-require + version: 1.1.1 + dev: true + registry.npmmirror.com/cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz} name: cross-spawn @@ -5621,6 +5899,24 @@ packages: version: 0.0.2 dev: false + registry.npmmirror.com/crypto-browserify@3.12.0: + resolution: {integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz} + name: crypto-browserify + version: 3.12.0 + dependencies: + browserify-cipher: registry.npmmirror.com/browserify-cipher@1.0.1 + browserify-sign: registry.npmmirror.com/browserify-sign@4.2.2 + create-ecdh: registry.npmmirror.com/create-ecdh@4.0.4 + create-hash: registry.npmmirror.com/create-hash@1.2.0 + create-hmac: registry.npmmirror.com/create-hmac@1.1.7 + diffie-hellman: registry.npmmirror.com/diffie-hellman@5.0.3 + inherits: registry.npmmirror.com/inherits@2.0.4 + pbkdf2: registry.npmmirror.com/pbkdf2@3.1.2 + public-encrypt: registry.npmmirror.com/public-encrypt@4.0.3 + randombytes: registry.npmmirror.com/randombytes@2.1.0 + randomfill: registry.npmmirror.com/randomfill@1.0.4 + dev: true + registry.npmmirror.com/css-box-model@1.2.1: resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/css-box-model/-/css-box-model-1.2.1.tgz} name: css-box-model @@ -6138,6 +6434,15 @@ packages: character-entities: registry.npmmirror.com/character-entities@2.0.2 dev: false + registry.npmmirror.com/deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/deep-eql/-/deep-eql-4.1.3.tgz} + name: deep-eql + version: 4.1.3 + engines: {node: '>=6'} + dependencies: + type-detect: registry.npmmirror.com/type-detect@4.0.8 + dev: true + registry.npmmirror.com/deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz} name: deep-is @@ -6200,6 +6505,15 @@ packages: version: 2.0.3 engines: {node: '>=6'} + registry.npmmirror.com/des.js@1.1.0: + resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/des.js/-/des.js-1.1.0.tgz} + name: des.js + version: 1.1.0 + dependencies: + inherits: registry.npmmirror.com/inherits@2.0.4 + minimalistic-assert: registry.npmmirror.com/minimalistic-assert@1.0.1 + dev: true + registry.npmmirror.com/detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/detect-node-es/-/detect-node-es-1.1.0.tgz} name: detect-node-es @@ -6222,6 +6536,16 @@ packages: engines: {node: '>=0.3.1'} dev: false + registry.npmmirror.com/diffie-hellman@5.0.3: + resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz} + name: diffie-hellman + version: 5.0.3 + dependencies: + bn.js: registry.npmmirror.com/bn.js@4.12.0 + miller-rabin: registry.npmmirror.com/miller-rabin@4.0.1 + randombytes: registry.npmmirror.com/randombytes@2.1.0 + dev: true + registry.npmmirror.com/digest-fetch@1.3.0: resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/digest-fetch/-/digest-fetch-1.3.0.tgz} name: digest-fetch @@ -6274,6 +6598,13 @@ packages: entities: registry.npmmirror.com/entities@2.2.0 dev: true + registry.npmmirror.com/domain-browser@4.23.0: + resolution: {integrity: sha512-ArzcM/II1wCCujdCNyQjXrAFwS4mrLh4C7DZWlaI8mdh7h3BfKdNd3bKXITfl2PT9FtfQqaGvhi1vPRQPimjGA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/domain-browser/-/domain-browser-4.23.0.tgz} + name: domain-browser + version: 4.23.0 + engines: {node: '>=10'} + dev: true + registry.npmmirror.com/domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz} name: domelementtype @@ -6375,6 +6706,20 @@ packages: version: 0.8.2 dev: false + registry.npmmirror.com/elliptic@6.5.4: + resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/elliptic/-/elliptic-6.5.4.tgz} + name: elliptic + version: 6.5.4 + dependencies: + bn.js: registry.npmmirror.com/bn.js@4.12.0 + brorand: registry.npmmirror.com/brorand@1.1.0 + hash.js: registry.npmmirror.com/hash.js@1.1.7 + hmac-drbg: registry.npmmirror.com/hmac-drbg@1.0.1 + inherits: registry.npmmirror.com/inherits@2.0.4 + minimalistic-assert: registry.npmmirror.com/minimalistic-assert@1.0.1 + minimalistic-crypto-utils: registry.npmmirror.com/minimalistic-crypto-utils@1.0.1 + dev: true + registry.npmmirror.com/emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz} name: emoji-regex @@ -6522,6 +6867,258 @@ packages: is-symbol: registry.npmmirror.com/is-symbol@1.0.4 dev: true + registry.npmmirror.com/esbuild-android-64@0.15.18: + resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz} + name: esbuild-android-64 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-android-arm64@0.15.18: + resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz} + name: esbuild-android-arm64 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-darwin-64@0.15.18: + resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz} + name: esbuild-darwin-64 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-darwin-arm64@0.15.18: + resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz} + name: esbuild-darwin-arm64 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-freebsd-64@0.15.18: + resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz} + name: esbuild-freebsd-64 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-freebsd-arm64@0.15.18: + resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz} + name: esbuild-freebsd-arm64 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-32@0.15.18: + resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz} + name: esbuild-linux-32 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-64@0.15.18: + resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz} + name: esbuild-linux-64 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-arm64@0.15.18: + resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz} + name: esbuild-linux-arm64 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-arm@0.15.18: + resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz} + name: esbuild-linux-arm + version: 0.15.18 + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-mips64le@0.15.18: + resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz} + name: esbuild-linux-mips64le + version: 0.15.18 + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-ppc64le@0.15.18: + resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz} + name: esbuild-linux-ppc64le + version: 0.15.18 + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-riscv64@0.15.18: + resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz} + name: esbuild-linux-riscv64 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-s390x@0.15.18: + resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz} + name: esbuild-linux-s390x + version: 0.15.18 + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-netbsd-64@0.15.18: + resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz} + name: esbuild-netbsd-64 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-openbsd-64@0.15.18: + resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz} + name: esbuild-openbsd-64 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-sunos-64@0.15.18: + resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz} + name: esbuild-sunos-64 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-windows-32@0.15.18: + resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz} + name: esbuild-windows-32 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-windows-64@0.15.18: + resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz} + name: esbuild-windows-64 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-windows-arm64@0.15.18: + resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz} + name: esbuild-windows-arm64 + version: 0.15.18 + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild@0.15.18: + resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild/-/esbuild-0.15.18.tgz} + name: esbuild + version: 0.15.18 + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': registry.npmmirror.com/@esbuild/android-arm@0.15.18 + '@esbuild/linux-loong64': registry.npmmirror.com/@esbuild/linux-loong64@0.15.18 + esbuild-android-64: registry.npmmirror.com/esbuild-android-64@0.15.18 + esbuild-android-arm64: registry.npmmirror.com/esbuild-android-arm64@0.15.18 + esbuild-darwin-64: registry.npmmirror.com/esbuild-darwin-64@0.15.18 + esbuild-darwin-arm64: registry.npmmirror.com/esbuild-darwin-arm64@0.15.18 + esbuild-freebsd-64: registry.npmmirror.com/esbuild-freebsd-64@0.15.18 + esbuild-freebsd-arm64: registry.npmmirror.com/esbuild-freebsd-arm64@0.15.18 + esbuild-linux-32: registry.npmmirror.com/esbuild-linux-32@0.15.18 + esbuild-linux-64: registry.npmmirror.com/esbuild-linux-64@0.15.18 + esbuild-linux-arm: registry.npmmirror.com/esbuild-linux-arm@0.15.18 + esbuild-linux-arm64: registry.npmmirror.com/esbuild-linux-arm64@0.15.18 + esbuild-linux-mips64le: registry.npmmirror.com/esbuild-linux-mips64le@0.15.18 + esbuild-linux-ppc64le: registry.npmmirror.com/esbuild-linux-ppc64le@0.15.18 + esbuild-linux-riscv64: registry.npmmirror.com/esbuild-linux-riscv64@0.15.18 + esbuild-linux-s390x: registry.npmmirror.com/esbuild-linux-s390x@0.15.18 + esbuild-netbsd-64: registry.npmmirror.com/esbuild-netbsd-64@0.15.18 + esbuild-openbsd-64: registry.npmmirror.com/esbuild-openbsd-64@0.15.18 + esbuild-sunos-64: registry.npmmirror.com/esbuild-sunos-64@0.15.18 + esbuild-windows-32: registry.npmmirror.com/esbuild-windows-32@0.15.18 + esbuild-windows-64: registry.npmmirror.com/esbuild-windows-64@0.15.18 + esbuild-windows-arm64: registry.npmmirror.com/esbuild-windows-arm64@0.15.18 + dev: true + registry.npmmirror.com/escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz} name: escalade @@ -6895,6 +7492,22 @@ packages: version: 5.0.1 dev: true + registry.npmmirror.com/events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/events/-/events-3.3.0.tgz} + name: events + version: 3.3.0 + engines: {node: '>=0.8.x'} + dev: true + + registry.npmmirror.com/evp_bytestokey@1.0.3: + resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz} + name: evp_bytestokey + version: 1.0.3 + dependencies: + md5.js: registry.npmmirror.com/md5.js@1.3.5 + safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 + dev: true + registry.npmmirror.com/execa@7.2.0: resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/execa/-/execa-7.2.0.tgz} name: execa @@ -6916,7 +7529,6 @@ packages: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz} name: extend version: 3.0.2 - dev: false registry.npmmirror.com/fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz} @@ -6963,7 +7575,6 @@ packages: version: 1.0.4 dependencies: format: registry.npmmirror.com/format@0.2.2 - dev: false registry.npmmirror.com/fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fecha/-/fecha-4.2.3.tgz} @@ -7078,7 +7689,6 @@ packages: name: format version: 0.2.2 engines: {node: '>=0.4.x'} - dev: false registry.npmmirror.com/formdata-node@4.4.1: resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/formdata-node/-/formdata-node-4.4.1.tgz} @@ -7169,6 +7779,12 @@ packages: version: 1.0.0-beta.2 engines: {node: '>=6.9.0'} + registry.npmmirror.com/get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/get-func-name/-/get-func-name-2.0.2.tgz} + name: get-func-name + version: 2.0.2 + dev: true + registry.npmmirror.com/get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz} name: get-intrinsic @@ -7368,6 +7984,26 @@ packages: version: 1.0.4 engines: {node: '>= 0.4.0'} + registry.npmmirror.com/hash-base@3.1.0: + resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/hash-base/-/hash-base-3.1.0.tgz} + name: hash-base + version: 3.1.0 + engines: {node: '>=4'} + dependencies: + inherits: registry.npmmirror.com/inherits@2.0.4 + readable-stream: registry.npmmirror.com/readable-stream@3.6.2 + safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 + dev: true + + registry.npmmirror.com/hash.js@1.1.7: + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/hash.js/-/hash.js-1.1.7.tgz} + name: hash.js + version: 1.1.7 + dependencies: + inherits: registry.npmmirror.com/inherits@2.0.4 + minimalistic-assert: registry.npmmirror.com/minimalistic-assert@1.0.1 + dev: true + registry.npmmirror.com/hast-util-from-dom@4.2.0: resolution: {integrity: sha512-t1RJW/OpJbCAJQeKi3Qrj1cAOLA0+av/iPFori112+0X7R3wng+jxLA+kXec8K4szqPRGI8vPxbbpEYvvpwaeQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/hast-util-from-dom/-/hast-util-from-dom-4.2.0.tgz} name: hast-util-from-dom @@ -7497,6 +8133,16 @@ packages: version: 10.7.3 dev: false + registry.npmmirror.com/hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz} + name: hmac-drbg + version: 1.0.1 + dependencies: + hash.js: registry.npmmirror.com/hash.js@1.1.7 + minimalistic-assert: registry.npmmirror.com/minimalistic-assert@1.0.1 + minimalistic-crypto-utils: registry.npmmirror.com/minimalistic-crypto-utils@1.0.1 + dev: true + registry.npmmirror.com/hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz} name: hoist-non-react-statics @@ -7533,6 +8179,12 @@ packages: - supports-color dev: false + registry.npmmirror.com/https-browserify@1.0.0: + resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/https-browserify/-/https-browserify-1.0.0.tgz} + name: https-browserify + version: 1.0.0 + dev: true + registry.npmmirror.com/https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz} name: https-proxy-agent @@ -7595,6 +8247,12 @@ packages: safer-buffer: registry.npmmirror.com/safer-buffer@2.1.2 dev: false + registry.npmmirror.com/ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz} + name: ieee754 + version: 1.2.1 + dev: true + registry.npmmirror.com/ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ignore/-/ignore-5.2.4.tgz} name: ignore @@ -7697,7 +8355,6 @@ packages: resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz} name: is-alphabetical version: 1.0.4 - dev: false registry.npmmirror.com/is-alphanumerical@1.0.4: resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz} @@ -7706,7 +8363,16 @@ packages: dependencies: is-alphabetical: registry.npmmirror.com/is-alphabetical@1.0.4 is-decimal: registry.npmmirror.com/is-decimal@1.0.4 - dev: false + + registry.npmmirror.com/is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-arguments/-/is-arguments-1.1.1.tgz} + name: is-arguments + version: 1.1.1 + engines: {node: '>= 0.4'} + dependencies: + call-bind: registry.npmmirror.com/call-bind@1.0.5 + has-tostringtag: registry.npmmirror.com/has-tostringtag@1.0.0 + dev: true registry.npmmirror.com/is-array-buffer@3.0.2: resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz} @@ -7775,7 +8441,6 @@ packages: name: is-buffer version: 2.0.5 engines: {node: '>=4'} - dev: false registry.npmmirror.com/is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz} @@ -7804,7 +8469,6 @@ packages: resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-decimal/-/is-decimal-1.0.4.tgz} name: is-decimal version: 1.0.4 - dev: false registry.npmmirror.com/is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz} @@ -7848,7 +8512,6 @@ packages: resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz} name: is-hexadecimal version: 1.0.4 - dev: false registry.npmmirror.com/is-map@2.0.2: resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-map/-/is-map-2.0.2.tgz} @@ -7856,6 +8519,16 @@ packages: version: 2.0.2 dev: true + registry.npmmirror.com/is-nan@1.3.2: + resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-nan/-/is-nan-1.3.2.tgz} + name: is-nan + version: 1.3.2 + engines: {node: '>= 0.4'} + dependencies: + call-bind: registry.npmmirror.com/call-bind@1.0.5 + define-properties: registry.npmmirror.com/define-properties@1.2.1 + dev: true + registry.npmmirror.com/is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz} name: is-negative-zero @@ -7885,6 +8558,13 @@ packages: engines: {node: '>=8'} dev: true + registry.npmmirror.com/is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz} + name: is-plain-obj + version: 2.1.0 + engines: {node: '>=8'} + dev: true + registry.npmmirror.com/is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz} name: is-plain-obj @@ -7986,6 +8666,18 @@ packages: get-intrinsic: registry.npmmirror.com/get-intrinsic@1.2.1 dev: true + registry.npmmirror.com/is-whitespace-character@1.0.4: + resolution: {integrity: sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz} + name: is-whitespace-character + version: 1.0.4 + dev: true + + registry.npmmirror.com/is-word-character@1.0.4: + resolution: {integrity: sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-word-character/-/is-word-character-1.0.4.tgz} + name: is-word-character + version: 1.0.4 + dev: true + registry.npmmirror.com/isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz} name: isarray @@ -8004,6 +8696,13 @@ packages: version: 2.0.0 dev: true + registry.npmmirror.com/isomorphic-timers-promises@1.0.1: + resolution: {integrity: sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/isomorphic-timers-promises/-/isomorphic-timers-promises-1.0.1.tgz} + name: isomorphic-timers-promises + version: 1.0.1 + engines: {node: '>=10'} + dev: true + registry.npmmirror.com/iterator.prototype@1.1.2: resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz} name: iterator.prototype @@ -8351,6 +9050,13 @@ packages: wrap-ansi: registry.npmmirror.com/wrap-ansi@8.1.0 dev: true + registry.npmmirror.com/local-pkg@0.4.3: + resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.3.tgz} + name: local-pkg + version: 0.4.3 + engines: {node: '>=14'} + dev: true + registry.npmmirror.com/locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz} name: locate-path @@ -8483,6 +9189,14 @@ packages: underscore: registry.npmmirror.com/underscore@1.13.6 dev: false + registry.npmmirror.com/loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/loupe/-/loupe-2.3.7.tgz} + name: loupe + version: 2.3.7 + dependencies: + get-func-name: registry.npmmirror.com/get-func-name@2.0.2 + dev: true + registry.npmmirror.com/lowlight@1.20.0: resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lowlight/-/lowlight-1.20.0.tgz} name: lowlight @@ -8526,12 +9240,28 @@ packages: xmlbuilder: registry.npmmirror.com/xmlbuilder@10.1.1 dev: false + registry.npmmirror.com/markdown-escapes@1.0.4: + resolution: {integrity: sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz} + name: markdown-escapes + version: 1.0.4 + dev: true + registry.npmmirror.com/markdown-table@3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/markdown-table/-/markdown-table-3.0.3.tgz} name: markdown-table version: 3.0.3 dev: false + registry.npmmirror.com/md5.js@1.3.5: + resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz} + name: md5.js + version: 1.3.5 + dependencies: + hash-base: registry.npmmirror.com/hash-base@3.1.0 + inherits: registry.npmmirror.com/inherits@2.0.4 + safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 + dev: true + registry.npmmirror.com/md5@2.3.0: resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/md5/-/md5-2.3.0.tgz} name: md5 @@ -9108,6 +9838,16 @@ packages: picomatch: registry.npmmirror.com/picomatch@2.3.1 dev: true + registry.npmmirror.com/miller-rabin@4.0.1: + resolution: {integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/miller-rabin/-/miller-rabin-4.0.1.tgz} + name: miller-rabin + version: 4.0.1 + hasBin: true + dependencies: + bn.js: registry.npmmirror.com/bn.js@4.12.0 + brorand: registry.npmmirror.com/brorand@1.1.0 + dev: true + registry.npmmirror.com/mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz} name: mime-db @@ -9138,6 +9878,18 @@ packages: engines: {node: '>=12'} dev: true + registry.npmmirror.com/minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz} + name: minimalistic-assert + version: 1.0.1 + dev: true + + registry.npmmirror.com/minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz} + name: minimalistic-crypto-utils + version: 1.0.1 + dev: true + registry.npmmirror.com/minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz} name: minimatch @@ -9432,6 +10184,41 @@ packages: name: node-releases version: 2.0.13 + registry.npmmirror.com/node-stdlib-browser@1.2.0: + resolution: {integrity: sha512-VSjFxUhRhkyed8AtLwSCkMrJRfQ3e2lGtG3sP6FEgaLKBBbxM/dLfjRe1+iLhjvyLFW3tBQ8+c0pcOtXGbAZJg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/node-stdlib-browser/-/node-stdlib-browser-1.2.0.tgz} + name: node-stdlib-browser + version: 1.2.0 + engines: {node: '>=10'} + dependencies: + assert: registry.npmmirror.com/assert@2.1.0 + browser-resolve: registry.npmmirror.com/browser-resolve@2.0.0 + browserify-zlib: registry.npmmirror.com/browserify-zlib@0.2.0 + buffer: registry.npmmirror.com/buffer@5.7.1 + console-browserify: registry.npmmirror.com/console-browserify@1.2.0 + constants-browserify: registry.npmmirror.com/constants-browserify@1.0.0 + create-require: registry.npmmirror.com/create-require@1.1.1 + crypto-browserify: registry.npmmirror.com/crypto-browserify@3.12.0 + domain-browser: registry.npmmirror.com/domain-browser@4.23.0 + events: registry.npmmirror.com/events@3.3.0 + https-browserify: registry.npmmirror.com/https-browserify@1.0.0 + isomorphic-timers-promises: registry.npmmirror.com/isomorphic-timers-promises@1.0.1 + os-browserify: registry.npmmirror.com/os-browserify@0.3.0 + path-browserify: registry.npmmirror.com/path-browserify@1.0.1 + pkg-dir: registry.npmmirror.com/pkg-dir@5.0.0 + process: registry.npmmirror.com/process@0.11.10 + punycode: registry.npmmirror.com/punycode@1.4.1 + querystring-es3: registry.npmmirror.com/querystring-es3@0.2.1 + readable-stream: registry.npmmirror.com/readable-stream@3.6.2 + stream-browserify: registry.npmmirror.com/stream-browserify@3.0.0 + stream-http: registry.npmmirror.com/stream-http@3.2.0 + string_decoder: registry.npmmirror.com/string_decoder@1.3.0 + timers-browserify: registry.npmmirror.com/timers-browserify@2.0.12 + tty-browserify: registry.npmmirror.com/tty-browserify@0.0.1 + url: registry.npmmirror.com/url@0.11.3 + util: registry.npmmirror.com/util@0.12.5 + vm-browserify: registry.npmmirror.com/vm-browserify@1.1.2 + dev: true + registry.npmmirror.com/non-layered-tidy-tree-layout@2.0.2: resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz} name: non-layered-tidy-tree-layout @@ -9484,6 +10271,16 @@ packages: name: object-inspect version: 1.13.1 + registry.npmmirror.com/object-is@1.1.5: + resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/object-is/-/object-is-1.1.5.tgz} + name: object-is + version: 1.1.5 + engines: {node: '>= 0.4'} + dependencies: + call-bind: registry.npmmirror.com/call-bind@1.0.5 + define-properties: registry.npmmirror.com/define-properties@1.2.1 + dev: true + registry.npmmirror.com/object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz} name: object-keys @@ -9595,11 +10392,11 @@ packages: mimic-fn: registry.npmmirror.com/mimic-fn@4.0.0 dev: true - registry.npmmirror.com/openai@4.12.4(encoding@0.1.13): - resolution: {integrity: sha512-oPNVJkpgxDUKF6WGGdHEZh5m/kjmYxS2Y1q7YVFCkvKUGthb8OGYRGCFBRPq5CQJezifzABTZRlVYnXLd6L4vQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/openai/-/openai-4.12.4.tgz} - id: registry.npmmirror.com/openai/4.12.4 + registry.npmmirror.com/openai@4.16.1(encoding@0.1.13): + resolution: {integrity: sha512-Gr+uqUN1ICSk6VhrX64E+zL7skjI1TgPr/XUN+ZQuNLLOvx15+XZulx/lSW4wFEAQzgjBDlMBbBeikguGIjiMg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/openai/-/openai-4.16.1.tgz} + id: registry.npmmirror.com/openai/4.16.1 name: openai - version: 4.12.4 + version: 4.16.1 hasBin: true dependencies: '@types/node': registry.npmmirror.com/@types/node@18.18.6 @@ -9644,6 +10441,12 @@ packages: type-check: registry.npmmirror.com/type-check@0.4.0 dev: true + registry.npmmirror.com/os-browserify@0.3.0: + resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/os-browserify/-/os-browserify-0.3.0.tgz} + name: os-browserify + version: 0.3.0 + dev: true + registry.npmmirror.com/p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz} name: p-limit @@ -9672,7 +10475,6 @@ packages: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz} name: pako version: 1.0.11 - dev: false registry.npmmirror.com/papaparse@5.4.1: resolution: {integrity: sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/papaparse/-/papaparse-5.4.1.tgz} @@ -9688,6 +10490,31 @@ packages: dependencies: callsites: registry.npmmirror.com/callsites@3.1.0 + registry.npmmirror.com/parse-asn1@5.1.6: + resolution: {integrity: sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/parse-asn1/-/parse-asn1-5.1.6.tgz} + name: parse-asn1 + version: 5.1.6 + dependencies: + asn1.js: registry.npmmirror.com/asn1.js@5.4.1 + browserify-aes: registry.npmmirror.com/browserify-aes@1.2.0 + evp_bytestokey: registry.npmmirror.com/evp_bytestokey@1.0.3 + pbkdf2: registry.npmmirror.com/pbkdf2@3.1.2 + safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 + dev: true + + registry.npmmirror.com/parse-entities@1.2.2: + resolution: {integrity: sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/parse-entities/-/parse-entities-1.2.2.tgz} + name: parse-entities + version: 1.2.2 + dependencies: + character-entities: registry.npmmirror.com/character-entities@1.2.4 + character-entities-legacy: registry.npmmirror.com/character-entities-legacy@1.1.4 + character-reference-invalid: registry.npmmirror.com/character-reference-invalid@1.1.4 + is-alphanumerical: registry.npmmirror.com/is-alphanumerical@1.0.4 + is-decimal: registry.npmmirror.com/is-decimal@1.0.4 + is-hexadecimal: registry.npmmirror.com/is-hexadecimal@1.0.4 + dev: true + registry.npmmirror.com/parse-entities@2.0.0: resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/parse-entities/-/parse-entities-2.0.0.tgz} name: parse-entities @@ -9719,6 +10546,12 @@ packages: dependencies: entities: registry.npmmirror.com/entities@4.5.0 + registry.npmmirror.com/path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz} + name: path-browserify + version: 1.0.1 + dev: true + registry.npmmirror.com/path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz} name: path-exists @@ -9757,6 +10590,25 @@ packages: version: 4.0.0 engines: {node: '>=8'} + registry.npmmirror.com/pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pathval/-/pathval-1.1.1.tgz} + name: pathval + version: 1.1.1 + dev: true + + registry.npmmirror.com/pbkdf2@3.1.2: + resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pbkdf2/-/pbkdf2-3.1.2.tgz} + name: pbkdf2 + version: 3.1.2 + engines: {node: '>=0.12'} + dependencies: + create-hash: registry.npmmirror.com/create-hash@1.2.0 + create-hmac: registry.npmmirror.com/create-hmac@1.1.7 + ripemd160: registry.npmmirror.com/ripemd160@2.0.2 + safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 + sha.js: registry.npmmirror.com/sha.js@2.4.11 + dev: true + registry.npmmirror.com/pg-cloudflare@1.1.1: resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz} name: pg-cloudflare @@ -9900,6 +10752,15 @@ packages: hasBin: true dev: true + registry.npmmirror.com/pkg-dir@5.0.0: + resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pkg-dir/-/pkg-dir-5.0.0.tgz} + name: pkg-dir + version: 5.0.0 + engines: {node: '>=10'} + dependencies: + find-up: registry.npmmirror.com/find-up@5.0.0 + dev: true + registry.npmmirror.com/postcss@8.4.14: resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/postcss/-/postcss-8.4.14.tgz} name: postcss @@ -9910,6 +10771,17 @@ packages: picocolors: registry.npmmirror.com/picocolors@1.0.0 source-map-js: registry.npmmirror.com/source-map-js@1.0.2 + registry.npmmirror.com/postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/postcss/-/postcss-8.4.31.tgz} + name: postcss + version: 8.4.31 + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: registry.npmmirror.com/nanoid@3.3.6 + picocolors: registry.npmmirror.com/picocolors@1.0.0 + source-map-js: registry.npmmirror.com/source-map-js@1.0.2 + dev: true + registry.npmmirror.com/postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/postgres-array/-/postgres-array-2.0.0.tgz} name: postgres-array @@ -10011,6 +10883,13 @@ packages: version: 2.0.1 dev: false + registry.npmmirror.com/process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/process/-/process-0.11.10.tgz} + name: process + version: 0.11.10 + engines: {node: '>= 0.6.0'} + dev: true + registry.npmmirror.com/prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz} name: prop-types @@ -10046,6 +10925,25 @@ packages: version: 1.9.0 dev: false + registry.npmmirror.com/public-encrypt@4.0.3: + resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/public-encrypt/-/public-encrypt-4.0.3.tgz} + name: public-encrypt + version: 4.0.3 + dependencies: + bn.js: registry.npmmirror.com/bn.js@4.12.0 + browserify-rsa: registry.npmmirror.com/browserify-rsa@4.1.0 + create-hash: registry.npmmirror.com/create-hash@1.2.0 + parse-asn1: registry.npmmirror.com/parse-asn1@5.1.6 + randombytes: registry.npmmirror.com/randombytes@2.1.0 + safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 + dev: true + + registry.npmmirror.com/punycode@1.4.1: + resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz} + name: punycode + version: 1.4.1 + dev: true + registry.npmmirror.com/punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/punycode/-/punycode-2.3.0.tgz} name: punycode @@ -10059,7 +10957,13 @@ packages: engines: {node: '>=0.6'} dependencies: side-channel: registry.npmmirror.com/side-channel@1.0.4 - dev: false + + registry.npmmirror.com/querystring-es3@0.2.1: + resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/querystring-es3/-/querystring-es3-0.2.1.tgz} + name: querystring-es3 + version: 0.2.1 + engines: {node: '>=0.4.x'} + dev: true registry.npmmirror.com/querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz} @@ -10073,6 +10977,23 @@ packages: version: 1.2.3 dev: true + registry.npmmirror.com/randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz} + name: randombytes + version: 2.1.0 + dependencies: + safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 + dev: true + + registry.npmmirror.com/randomfill@1.0.4: + resolution: {integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/randomfill/-/randomfill-1.0.4.tgz} + name: randomfill + version: 1.0.4 + dependencies: + randombytes: registry.npmmirror.com/randombytes@2.1.0 + safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 + dev: true + registry.npmmirror.com/react-clientside-effect@1.2.6(react@18.2.0): resolution: {integrity: sha512-XGGGRQAKY+q25Lz9a/4EPqom7WRjz3z9R2k4jhVKA/puQFH/5Nt27vFZYql4m4NVNdUvX8PS3O7r/Zzm7cjUlg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz} id: registry.npmmirror.com/react-clientside-effect/1.2.6 @@ -10343,7 +11264,6 @@ packages: inherits: registry.npmmirror.com/inherits@2.0.4 string_decoder: registry.npmmirror.com/string_decoder@1.3.0 util-deprecate: registry.npmmirror.com/util-deprecate@1.0.2 - dev: false registry.npmmirror.com/readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz} @@ -10469,6 +11389,15 @@ packages: unified: registry.npmmirror.com/unified@10.1.2 dev: false + registry.npmmirror.com/remark-frontmatter@1.3.3: + resolution: {integrity: sha512-fM5eZPBvu2pVNoq3ZPW22q+5Ativ1oLozq2qYt9I2oNyxiUd/tDl0iLLntEVAegpZIslPWg1brhcP1VsaSVUag==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/remark-frontmatter/-/remark-frontmatter-1.3.3.tgz} + name: remark-frontmatter + version: 1.3.3 + dependencies: + fault: registry.npmmirror.com/fault@1.0.4 + xtend: registry.npmmirror.com/xtend@4.0.2 + dev: true + registry.npmmirror.com/remark-gfm@3.0.1: resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/remark-gfm/-/remark-gfm-3.0.1.tgz} name: remark-gfm @@ -10505,6 +11434,28 @@ packages: - supports-color dev: false + registry.npmmirror.com/remark-parse@7.0.2: + resolution: {integrity: sha512-9+my0lQS80IQkYXsMA8Sg6m9QfXYJBnXjWYN5U+kFc5/n69t+XZVXU/ZBYr3cYH8FheEGf1v87rkFDhJ8bVgMA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/remark-parse/-/remark-parse-7.0.2.tgz} + name: remark-parse + version: 7.0.2 + dependencies: + collapse-white-space: registry.npmmirror.com/collapse-white-space@1.0.6 + is-alphabetical: registry.npmmirror.com/is-alphabetical@1.0.4 + is-decimal: registry.npmmirror.com/is-decimal@1.0.4 + is-whitespace-character: registry.npmmirror.com/is-whitespace-character@1.0.4 + is-word-character: registry.npmmirror.com/is-word-character@1.0.4 + markdown-escapes: registry.npmmirror.com/markdown-escapes@1.0.4 + parse-entities: registry.npmmirror.com/parse-entities@1.2.2 + repeat-string: registry.npmmirror.com/repeat-string@1.6.1 + state-toggle: registry.npmmirror.com/state-toggle@1.0.3 + trim: registry.npmmirror.com/trim@0.0.1 + trim-trailing-lines: registry.npmmirror.com/trim-trailing-lines@1.1.4 + unherit: registry.npmmirror.com/unherit@1.1.3 + unist-util-remove-position: registry.npmmirror.com/unist-util-remove-position@1.1.4 + vfile-location: registry.npmmirror.com/vfile-location@2.0.6 + xtend: registry.npmmirror.com/xtend@4.0.2 + dev: true + registry.npmmirror.com/remark-rehype@10.1.0: resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/remark-rehype/-/remark-rehype-10.1.0.tgz} name: remark-rehype @@ -10516,6 +11467,13 @@ packages: unified: registry.npmmirror.com/unified@10.1.2 dev: false + registry.npmmirror.com/repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/repeat-string/-/repeat-string-1.6.1.tgz} + name: repeat-string + version: 1.6.1 + engines: {node: '>=0.10'} + dev: true + registry.npmmirror.com/request-ip@3.3.0: resolution: {integrity: sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/request-ip/-/request-ip-3.3.0.tgz} name: request-ip @@ -10600,12 +11558,31 @@ packages: glob: registry.npmmirror.com/glob@7.2.3 dev: true + registry.npmmirror.com/ripemd160@2.0.2: + resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ripemd160/-/ripemd160-2.0.2.tgz} + name: ripemd160 + version: 2.0.2 + dependencies: + hash-base: registry.npmmirror.com/hash-base@3.1.0 + inherits: registry.npmmirror.com/inherits@2.0.4 + dev: true + registry.npmmirror.com/robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.2.tgz} name: robust-predicates version: 3.0.2 dev: false + registry.npmmirror.com/rollup@2.79.1: + resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/rollup/-/rollup-2.79.1.tgz} + name: rollup + version: 2.79.1 + engines: {node: '>=10.0.0'} + hasBin: true + optionalDependencies: + fsevents: registry.npmmirror.com/fsevents@2.3.3 + dev: true + registry.npmmirror.com/rrweb-cssom@0.6.0: resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz} name: rrweb-cssom @@ -10657,7 +11634,6 @@ packages: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz} name: safe-buffer version: 5.2.1 - dev: false registry.npmmirror.com/safe-regex-test@1.0.0: resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz} @@ -10680,7 +11656,6 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz} name: safer-buffer version: 2.1.2 - dev: false registry.npmmirror.com/saslprep@1.0.3: resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/saslprep/-/saslprep-1.0.3.tgz} @@ -10761,7 +11736,16 @@ packages: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz} name: setimmediate version: 1.0.5 - dev: false + + registry.npmmirror.com/sha.js@2.4.11: + resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sha.js/-/sha.js-2.4.11.tgz} + name: sha.js + version: 2.4.11 + hasBin: true + dependencies: + inherits: registry.npmmirror.com/inherits@2.0.4 + safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 + dev: true registry.npmmirror.com/shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz} @@ -10910,6 +11894,32 @@ packages: version: 0.0.10 dev: false + registry.npmmirror.com/state-toggle@1.0.3: + resolution: {integrity: sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/state-toggle/-/state-toggle-1.0.3.tgz} + name: state-toggle + version: 1.0.3 + dev: true + + registry.npmmirror.com/stream-browserify@3.0.0: + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/stream-browserify/-/stream-browserify-3.0.0.tgz} + name: stream-browserify + version: 3.0.0 + dependencies: + inherits: registry.npmmirror.com/inherits@2.0.4 + readable-stream: registry.npmmirror.com/readable-stream@3.6.2 + dev: true + + registry.npmmirror.com/stream-http@3.2.0: + resolution: {integrity: sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/stream-http/-/stream-http-3.2.0.tgz} + name: stream-http + version: 3.2.0 + dependencies: + builtin-status-codes: registry.npmmirror.com/builtin-status-codes@3.0.0 + inherits: registry.npmmirror.com/inherits@2.0.4 + readable-stream: registry.npmmirror.com/readable-stream@3.6.2 + xtend: registry.npmmirror.com/xtend@4.0.2 + dev: true + registry.npmmirror.com/streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/streamsearch/-/streamsearch-1.1.0.tgz} name: streamsearch @@ -10995,7 +12005,6 @@ packages: version: 1.3.0 dependencies: safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1 - dev: false registry.npmmirror.com/strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz} @@ -11146,6 +12155,15 @@ packages: version: 0.2.0 dev: true + registry.npmmirror.com/timers-browserify@2.0.12: + resolution: {integrity: sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/timers-browserify/-/timers-browserify-2.0.12.tgz} + name: timers-browserify + version: 2.0.12 + engines: {node: '>=0.6.0'} + dependencies: + setimmediate: registry.npmmirror.com/setimmediate@1.0.5 + dev: true + registry.npmmirror.com/timezones-list@3.0.2: resolution: {integrity: sha512-I698hm6Jp/xxkwyTSOr39pZkYKETL8LDJeSIhjxXBfPUAHM5oZNuQ4o9UK3PSkDBOkjATecSOBb3pR1IkIBUsg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/timezones-list/-/timezones-list-3.0.2.tgz} name: timezones-list @@ -11158,6 +12176,20 @@ packages: version: 1.3.1 dev: false + registry.npmmirror.com/tinypool@0.2.4: + resolution: {integrity: sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tinypool/-/tinypool-0.2.4.tgz} + name: tinypool + version: 0.2.4 + engines: {node: '>=14.0.0'} + dev: true + + registry.npmmirror.com/tinyspy@1.1.1: + resolution: {integrity: sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tinyspy/-/tinyspy-1.1.1.tgz} + name: tinyspy + version: 1.1.1 + engines: {node: '>=14.0.0'} + dev: true + registry.npmmirror.com/to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz} name: to-fast-properties @@ -11220,6 +12252,19 @@ packages: version: 3.0.1 dev: false + registry.npmmirror.com/trim-trailing-lines@1.1.4: + resolution: {integrity: sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz} + name: trim-trailing-lines + version: 1.1.4 + dev: true + + registry.npmmirror.com/trim@0.0.1: + resolution: {integrity: sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/trim/-/trim-0.0.1.tgz} + name: trim + version: 0.0.1 + deprecated: Use String.prototype.trim() instead + dev: true + registry.npmmirror.com/triple-beam@1.4.1: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/triple-beam/-/triple-beam-1.4.1.tgz} name: triple-beam @@ -11227,6 +12272,12 @@ packages: engines: {node: '>= 14.0.0'} dev: false + registry.npmmirror.com/trough@1.0.5: + resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/trough/-/trough-1.0.5.tgz} + name: trough + version: 1.0.5 + dev: true + registry.npmmirror.com/trough@2.1.0: resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/trough/-/trough-2.1.0.tgz} name: trough @@ -11287,6 +12338,12 @@ packages: typescript: registry.npmmirror.com/typescript@4.9.5 dev: true + registry.npmmirror.com/tty-browserify@0.0.1: + resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tty-browserify/-/tty-browserify-0.0.1.tgz} + name: tty-browserify + version: 0.0.1 + dev: true + registry.npmmirror.com/tunnel@0.0.6: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tunnel/-/tunnel-0.0.6.tgz} name: tunnel @@ -11303,6 +12360,13 @@ packages: prelude-ls: registry.npmmirror.com/prelude-ls@1.2.1 dev: true + registry.npmmirror.com/type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/type-detect/-/type-detect-4.0.8.tgz} + name: type-detect + version: 4.0.8 + engines: {node: '>=4'} + dev: true + registry.npmmirror.com/type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz} name: type-fest @@ -11409,6 +12473,15 @@ packages: name: undici-types version: 5.25.3 + registry.npmmirror.com/unherit@1.1.3: + resolution: {integrity: sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unherit/-/unherit-1.1.3.tgz} + name: unherit + version: 1.1.3 + dependencies: + inherits: registry.npmmirror.com/inherits@2.0.4 + xtend: registry.npmmirror.com/xtend@4.0.2 + dev: true + registry.npmmirror.com/unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz} name: unicode-canonical-property-names-ecmascript @@ -11454,6 +12527,19 @@ packages: vfile: registry.npmmirror.com/vfile@5.3.7 dev: false + registry.npmmirror.com/unified@8.4.2: + resolution: {integrity: sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unified/-/unified-8.4.2.tgz} + name: unified + version: 8.4.2 + dependencies: + '@types/unist': registry.npmmirror.com/@types/unist@2.0.9 + bail: registry.npmmirror.com/bail@1.0.5 + extend: registry.npmmirror.com/extend@3.0.2 + is-plain-obj: registry.npmmirror.com/is-plain-obj@2.1.0 + trough: registry.npmmirror.com/trough@1.0.5 + vfile: registry.npmmirror.com/vfile@4.2.1 + dev: true + registry.npmmirror.com/unist-util-find-after@4.0.1: resolution: {integrity: sha512-QO/PuPMm2ERxC6vFXEPtmAutOopy5PknD+Oq64gGwxKtk4xwo9Z97t9Av1obPmGU0IyTa6EKYUfTrK2QJS3Ozw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unist-util-find-after/-/unist-util-find-after-4.0.1.tgz} name: unist-util-find-after @@ -11469,6 +12555,12 @@ packages: version: 2.0.1 dev: false + registry.npmmirror.com/unist-util-is@3.0.0: + resolution: {integrity: sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unist-util-is/-/unist-util-is-3.0.0.tgz} + name: unist-util-is + version: 3.0.0 + dev: true + registry.npmmirror.com/unist-util-is@5.2.1: resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unist-util-is/-/unist-util-is-5.2.1.tgz} name: unist-util-is @@ -11485,6 +12577,14 @@ packages: '@types/unist': registry.npmmirror.com/@types/unist@2.0.9 dev: false + registry.npmmirror.com/unist-util-remove-position@1.1.4: + resolution: {integrity: sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz} + name: unist-util-remove-position + version: 1.1.4 + dependencies: + unist-util-visit: registry.npmmirror.com/unist-util-visit@1.4.1 + dev: true + registry.npmmirror.com/unist-util-remove-position@4.0.2: resolution: {integrity: sha512-TkBb0HABNmxzAcfLf4qsIbFbaPDvMO6wa3b3j4VcEzFVaw1LBKwnW4/sRJ/atSLSzoIg41JWEdnE7N6DIhGDGQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unist-util-remove-position/-/unist-util-remove-position-4.0.2.tgz} name: unist-util-remove-position @@ -11494,6 +12594,14 @@ packages: unist-util-visit: registry.npmmirror.com/unist-util-visit@4.1.2 dev: false + registry.npmmirror.com/unist-util-stringify-position@2.0.3: + resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz} + name: unist-util-stringify-position + version: 2.0.3 + dependencies: + '@types/unist': registry.npmmirror.com/@types/unist@2.0.9 + dev: true + registry.npmmirror.com/unist-util-stringify-position@3.0.3: resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz} name: unist-util-stringify-position @@ -11502,6 +12610,14 @@ packages: '@types/unist': registry.npmmirror.com/@types/unist@2.0.9 dev: false + registry.npmmirror.com/unist-util-visit-parents@2.1.2: + resolution: {integrity: sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz} + name: unist-util-visit-parents + version: 2.1.2 + dependencies: + unist-util-is: registry.npmmirror.com/unist-util-is@3.0.0 + dev: true + registry.npmmirror.com/unist-util-visit-parents@5.1.3: resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz} name: unist-util-visit-parents @@ -11511,6 +12627,14 @@ packages: unist-util-is: registry.npmmirror.com/unist-util-is@5.2.1 dev: false + registry.npmmirror.com/unist-util-visit@1.4.1: + resolution: {integrity: sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz} + name: unist-util-visit + version: 1.4.1 + dependencies: + unist-util-visit-parents: registry.npmmirror.com/unist-util-visit-parents@2.1.2 + dev: true + registry.npmmirror.com/unist-util-visit@4.1.2: resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz} name: unist-util-visit @@ -11558,6 +12682,15 @@ packages: requires-port: registry.npmmirror.com/requires-port@1.0.0 dev: false + registry.npmmirror.com/url@0.11.3: + resolution: {integrity: sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/url/-/url-0.11.3.tgz} + name: url + version: 0.11.3 + dependencies: + punycode: registry.npmmirror.com/punycode@1.4.1 + qs: registry.npmmirror.com/qs@6.11.2 + dev: true + registry.npmmirror.com/use-callback-ref@1.3.0(@types/react@18.0.28)(react@18.2.0): resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/use-callback-ref/-/use-callback-ref-1.3.0.tgz} id: registry.npmmirror.com/use-callback-ref/1.3.0 @@ -11610,7 +12743,18 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz} name: util-deprecate version: 1.0.2 - dev: false + + registry.npmmirror.com/util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/util/-/util-0.12.5.tgz} + name: util + version: 0.12.5 + dependencies: + inherits: registry.npmmirror.com/inherits@2.0.4 + is-arguments: registry.npmmirror.com/is-arguments@1.1.1 + is-generator-function: registry.npmmirror.com/is-generator-function@1.0.10 + is-typed-array: registry.npmmirror.com/is-typed-array@1.1.12 + which-typed-array: registry.npmmirror.com/which-typed-array@1.1.13 + dev: true registry.npmmirror.com/uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz} @@ -11639,6 +12783,12 @@ packages: engines: {node: '>= 0.8'} dev: false + registry.npmmirror.com/vfile-location@2.0.6: + resolution: {integrity: sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vfile-location/-/vfile-location-2.0.6.tgz} + name: vfile-location + version: 2.0.6 + dev: true + registry.npmmirror.com/vfile-location@4.1.0: resolution: {integrity: sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vfile-location/-/vfile-location-4.1.0.tgz} name: vfile-location @@ -11648,6 +12798,15 @@ packages: vfile: registry.npmmirror.com/vfile@5.3.7 dev: false + registry.npmmirror.com/vfile-message@2.0.4: + resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vfile-message/-/vfile-message-2.0.4.tgz} + name: vfile-message + version: 2.0.4 + dependencies: + '@types/unist': registry.npmmirror.com/@types/unist@2.0.9 + unist-util-stringify-position: registry.npmmirror.com/unist-util-stringify-position@2.0.3 + dev: true + registry.npmmirror.com/vfile-message@3.1.4: resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vfile-message/-/vfile-message-3.1.4.tgz} name: vfile-message @@ -11657,6 +12816,17 @@ packages: unist-util-stringify-position: registry.npmmirror.com/unist-util-stringify-position@3.0.3 dev: false + registry.npmmirror.com/vfile@4.2.1: + resolution: {integrity: sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vfile/-/vfile-4.2.1.tgz} + name: vfile + version: 4.2.1 + dependencies: + '@types/unist': registry.npmmirror.com/@types/unist@2.0.9 + is-buffer: registry.npmmirror.com/is-buffer@2.0.5 + unist-util-stringify-position: registry.npmmirror.com/unist-util-stringify-position@2.0.3 + vfile-message: registry.npmmirror.com/vfile-message@2.0.4 + dev: true + registry.npmmirror.com/vfile@5.3.7: resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vfile/-/vfile-5.3.7.tgz} name: vfile @@ -11668,6 +12838,94 @@ packages: vfile-message: registry.npmmirror.com/vfile-message@3.1.4 dev: false + registry.npmmirror.com/vite@3.2.7(@types/node@20.8.7): + resolution: {integrity: sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vite/-/vite-3.2.7.tgz} + id: registry.npmmirror.com/vite/3.2.7 + name: vite + version: 3.2.7 + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': registry.npmmirror.com/@types/node@20.8.7 + esbuild: registry.npmmirror.com/esbuild@0.15.18 + postcss: registry.npmmirror.com/postcss@8.4.31 + resolve: registry.npmmirror.com/resolve@1.22.8 + rollup: registry.npmmirror.com/rollup@2.79.1 + optionalDependencies: + fsevents: registry.npmmirror.com/fsevents@2.3.3 + dev: true + + registry.npmmirror.com/vitest@0.21.1: + resolution: {integrity: sha512-WBIxuFmIDPuK47GO6Lu9eNeRMqHj/FWL3dk73OHH3eyPPWPiu+UB3QHLkLK2PEggCqJW4FaWoWg8R68S7p9+9Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vitest/-/vitest-0.21.1.tgz} + name: vitest + version: 0.21.1 + engines: {node: '>=v14.16.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + c8: '*' + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + c8: + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/chai': registry.npmmirror.com/@types/chai@4.3.9 + '@types/chai-subset': registry.npmmirror.com/@types/chai-subset@1.3.4 + '@types/node': registry.npmmirror.com/@types/node@20.8.7 + chai: registry.npmmirror.com/chai@4.3.10 + debug: registry.npmmirror.com/debug@4.3.4 + local-pkg: registry.npmmirror.com/local-pkg@0.4.3 + tinypool: registry.npmmirror.com/tinypool@0.2.4 + tinyspy: registry.npmmirror.com/tinyspy@1.1.1 + vite: registry.npmmirror.com/vite@3.2.7(@types/node@20.8.7) + transitivePeerDependencies: + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + registry.npmmirror.com/vm-browserify@1.1.2: + resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vm-browserify/-/vm-browserify-1.1.2.tgz} + name: vm-browserify + version: 1.1.2 + dev: true + registry.npmmirror.com/void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz} name: void-elements @@ -11949,7 +13207,6 @@ packages: name: xtend version: 4.0.2 engines: {node: '>=0.4'} - dev: false registry.npmmirror.com/yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz} @@ -11981,6 +13238,35 @@ packages: engines: {node: '>=10'} dev: true + registry.npmmirror.com/zhlint@0.7.1: + resolution: {integrity: sha512-FwwBm1JKyvIBm16exTqyG5gfnvp1fCKn9hIkjXj3cmbCn3aWE6FQaPTkmJfrLR0JNP1CIZjBDdD5Wkbts2r8PA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/zhlint/-/zhlint-0.7.1.tgz} + name: zhlint + version: 0.7.1 + hasBin: true + dependencies: + chalk: registry.npmmirror.com/chalk@4.1.2 + glob: registry.npmmirror.com/glob@7.2.3 + minimist: registry.npmmirror.com/minimist@1.2.8 + node-stdlib-browser: registry.npmmirror.com/node-stdlib-browser@1.2.0 + remark-frontmatter: registry.npmmirror.com/remark-frontmatter@1.3.3 + remark-parse: registry.npmmirror.com/remark-parse@7.0.2 + unified: registry.npmmirror.com/unified@8.4.2 + vitest: registry.npmmirror.com/vitest@0.21.1 + transitivePeerDependencies: + - '@edge-runtime/vm' + - '@vitest/browser' + - '@vitest/ui' + - c8 + - happy-dom + - jsdom + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + registry.npmmirror.com/zod@3.21.4: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/zod/-/zod-3.21.4.tgz} name: zod diff --git a/projects/app/data/config.json b/projects/app/data/config.json index 97837f48f..83bb305ac 100644 --- a/projects/app/data/config.json +++ b/projects/app/data/config.json @@ -1,15 +1,17 @@ { "SystemParams": { + "pluginBaseUrl": "", "vectorMaxProcess": 15, "qaMaxProcess": 15, "pgHNSWEfSearch": 100 }, "ChatModels": [ { - "model": "gpt-3.5-turbo", - "name": "GPT35-4k", + "model": "gpt-3.5-turbo-1106", + "name": "GPT35-1106", "price": 0, - "maxToken": 4000, + "maxContext": 16000, + "maxResponse": 4000, "quoteMaxToken": 2000, "maxTemperature": 1.2, "censor": false, @@ -18,7 +20,8 @@ { "model": "gpt-3.5-turbo-16k", "name": "GPT35-16k", - "maxToken": 16000, + "maxContext": 16000, + "maxResponse": 16000, "price": 0, "quoteMaxToken": 8000, "maxTemperature": 1.2, @@ -28,7 +31,8 @@ { "model": "gpt-4", "name": "GPT4-8k", - "maxToken": 8000, + "maxContext": 8000, + "maxResponse": 8000, "price": 0, "quoteMaxToken": 4000, "maxTemperature": 1.2, @@ -40,15 +44,17 @@ { "model": "gpt-3.5-turbo-16k", "name": "GPT35-16k", - "maxToken": 16000, + "maxContext": 16000, + "maxResponse": 16000, "price": 0 } ], "CQModels": [ { - "model": "gpt-3.5-turbo-16k", - "name": "GPT35-16k", - "maxToken": 16000, + "model": "gpt-3.5-turbo-1106", + "name": "GPT35-1106", + "maxContext": 16000, + "maxResponse": 4000, "price": 0, "functionCall": true, "functionPrompt": "" @@ -56,7 +62,8 @@ { "model": "gpt-4", "name": "GPT4-8k", - "maxToken": 8000, + "maxContext": 8000, + "maxResponse": 8000, "price": 0, "functionCall": true, "functionPrompt": "" @@ -64,9 +71,10 @@ ], "ExtractModels": [ { - "model": "gpt-3.5-turbo-16k", - "name": "GPT35-16k", - "maxToken": 16000, + "model": "gpt-3.5-turbo-1106", + "name": "GPT35-1106", + "maxContext": 16000, + "maxResponse": 4000, "price": 0, "functionCall": true, "functionPrompt": "" @@ -74,9 +82,10 @@ ], "QGModels": [ { - "model": "gpt-3.5-turbo", - "name": "GPT35-4K", - "maxToken": 4000, + "model": "gpt-3.5-turbo-1106", + "name": "GPT35-1106", + "maxContext": 1600, + "maxResponse": 4000, "price": 0 } ], @@ -88,5 +97,17 @@ "defaultToken": 700, "maxToken": 3000 } + ], + "AudioSpeechModels": [ + { + "model": "tts-1", + "name": "OpenAI TTS1", + "price": 0 + }, + { + "model": "tts-1-hd", + "name": "OpenAI TTS1HD", + "price": 0 + } ] } diff --git a/projects/app/next.config.js b/projects/app/next.config.js index 2cb8c0e64..750645d12 100644 --- a/projects/app/next.config.js +++ b/projects/app/next.config.js @@ -25,7 +25,8 @@ const nextConfig = { 'mongodb-client-encryption': false, kerberos: false, 'supports-color': false, - 'bson-ext': false + 'bson-ext': false, + 'pg-native': false }); config.module = { ...config.module, @@ -44,7 +45,7 @@ const nextConfig = { }, transpilePackages: ['@fastgpt/*'], experimental: { - serverComponentsExternalPackages: ['mongoose'], + serverComponentsExternalPackages: ['mongoose', 'winston', 'winston-mongodb', 'pg'], outputFileTracingRoot: path.join(__dirname, '../../') } }; diff --git a/projects/app/package.json b/projects/app/package.json index 96891d8ec..38fb44272 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "4.5.2", + "version": "4.6", "private": false, "scripts": { "dev": "next dev", @@ -47,7 +47,6 @@ "next-i18next": "^13.3.0", "nprogress": "^0.2.0", "papaparse": "^5.4.1", - "pg": "^8.10.0", "pg-query-stream": "^4.5.3", "react": "18.2.0", "react-day-picker": "^8.7.1", @@ -76,7 +75,6 @@ "@types/multer": "^1.4.7", "@types/node": "^20.8.5", "@types/papaparse": "^5.3.7", - "@types/pg": "^8.6.6", "@types/react": "18.0.28", "@types/react-dom": "18.0.11", "@types/react-syntax-highlighter": "^15.5.6", diff --git a/projects/app/public/docs/chatProblem.md b/projects/app/public/docs/chatProblem.md index 99256c060..7bd770887 100644 --- a/projects/app/public/docs/chatProblem.md +++ b/projects/app/public/docs/chatProblem.md @@ -1,12 +1,12 @@ ### 常见问题 - [**Git 地址**,点击查看项目地址](https://github.com/labring/FastGPT) -- [本地部署 FastGPT](https://doc.fastgpt.run/docs/installation) -- [API 文档](https://doc.fastgpt.run/docs/development/openapi?pre_pathname=%2Fdrive%2Fhome%2F) +- [本地部署 FastGPT](https://doc.fastgpt.in/docs/installation) +- [API 文档](https://doc.fastgpt.in/docs/development/openapi?pre_pathname=%2Fdrive%2Fhome%2F) - **反馈问卷**: 如果你遇到任何使用问题或有期望的功能,可以[填写该问卷](https://www.wjx.cn/vm/rLIw1uD.aspx#) - **问题文档**: [先看文档,再提问](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh) -- [点击查看商业版文档](https://doc.fastgpt.run/docs/commercial) -- [计费规则](https://doc.fastgpt.run/docs/pricing/) +- [点击查看商业版文档](https://doc.fastgpt.in/docs/commercial) +- [计费规则](https://doc.fastgpt.in/docs/pricing/) **其他问题** | 添加小助手进入交流群 | diff --git a/projects/app/public/docs/versionIntro.md b/projects/app/public/docs/versionIntro.md index f84539e84..e4427ffbc 100644 --- a/projects/app/public/docs/versionIntro.md +++ b/projects/app/public/docs/versionIntro.md @@ -1,7 +1,7 @@ -### Fast GPT V4.5.2 +### Fast GPT V4.6 -1. 新增 - 模块插件,允许自行组装插件进行模块复用。 -2. 优化 - 知识库引用提示。 +1. 新增 - 团队空间试用版。 +2. 新增 - OpenAI语音播报。 3. [知识库结构详解](https://doc.fastgpt.in/docs/use-cases/datasetengine/) 4. [知识库提示词详解](https://doc.fastgpt.in/docs/use-cases/ai_settings/#引用模板--引用提示词) 5. [使用文档](https://doc.fastgpt.in/docs/intro/) diff --git a/projects/app/public/locales/en/common.json b/projects/app/public/locales/en/common.json index 4f23d11ff..5b640a465 100644 --- a/projects/app/public/locales/en/common.json +++ b/projects/app/public/locales/en/common.json @@ -45,6 +45,8 @@ "Open AI Advanced Settings": "Advanced Settings", "Output Field Settings": "Output Field Settings", "Paste Config": "Paste Config", + "To Chat": "To Chat Page", + "To Settings": "To Settings Page", "Variable Key Repeat Tip": "Variable Key Repeat", "module": { "Combine Modules": "Combine Modules", @@ -70,7 +72,7 @@ "Admin Mark Content": "Corrected response", "Complete Response": "Complete Response", "Confirm to clear history": "Confirm to clear history?", - "Confirm to clear share chat histroy": " Are you sure to delete all chats?", + "Confirm to clear share chat history": " Are you sure to delete all chats?", "Exit Chat": "Exit", "Feedback Close": "Close Feedback", "Feedback Failed": "Feedback Failed", @@ -128,8 +130,9 @@ "Add": "Add", "Back": "Back", "Beta": "Beta", + "Business edition features": "This is the commercial version function ~", "Choose": "Choose", - "Close": "Clow", + "Close": "Close", "Collect": "Collect", "Confirm Create": "Create", "Confirm Move": "Move here", @@ -146,6 +149,7 @@ "Delete Success": "Delete Successful", "Delete Tip": "Delete Confirm", "Delete Warning": "Warning", + "Done": "Done", "Edit": "Edit", "Expired Time": "Expired", "File": "File", @@ -164,24 +168,29 @@ "Output": "Output", "Params": "Params", "Password inconsistency": "Password inconsistency", + "Please Input Name": "Please Input Name", "Rename": "Rename", "Rename Failed": "Rename Failed", "Rename Success": "Rename Success", "Request Error": "Request Error", + "Save Failed": "Save Failed", + "Save Success": "Save Success", "Search": "Search", "Select File Failed": "Select File Failed", "Select One Folder": "Select a folder", "Set Avatar": "Set Avatar", "Set Name": "Make a nice name", "Status": "Status", + "Team": "Team", "Test": "Test", "Time": "Time", - "Unknow": "Unknow", - "Unknow Source": "UnKnow Source", + "UnKnow": "UnKnow", + "UnKnow Source": "UnKnow Source", "Update Failed": "Update Failed", "Update Success": "Update Success", "Update Successful": "Update Successful", "Update Time": "Update Time", + "Username": "UserName", "error": { "unKnow": "There was an accident" }, @@ -191,6 +200,9 @@ "Move Success": "Move Success", "No Folder": "There's no subdirectory. Just put it here", "Root Path": "Root Folder" + }, + "input": { + "Repeat Value": "Repeat Value" } }, "core": { @@ -199,13 +211,26 @@ "Prompt": "Prompt" }, "app": { - "Next Step Guide": "Next step guide" + "Next Step Guide": "Next step guide", + "Question Guide Tip": "At the end of the conversation, three leading questions will be asked.", + "TTS": "Audio Speech", + "TTS Tip": "After this function is enabled, the voice playback function can be used after each conversation. Use of this feature may incur additional charges.", + "tts": { + "Close": "NoUse", + "Model alloy": "Female - Alloy", + "Model echo": "Male - Echo", + "Test Listen": "Test", + "Test Listen Text": "Hello, this is FastGPT, how can I help you?", + "Web": "Browser (free)" + } }, "chat": { - "Restart": "Restart" + "Audio Speech Error": "Audio Speech Error", + "Restart": "Restart", + "Send Message": "Send Message" }, "dataset": { - "Choose Dataset": "Chookse Dataset", + "Choose Dataset": "Choose Dataset", "Dataset": "Dataset", "Go Dataset": "To Dataset", "Quote Length": "Quote Length", @@ -262,6 +287,8 @@ "Select One Collection To Store": "Select the collection to store" }, "data": { + "Can not delete tip": "No modification permission", + "Can not edit": "No edit permission", "Delete Tip": "Confirm to delete the data?", "File import": "File Import", "Input Data": "Import Data", @@ -272,6 +299,11 @@ "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!" }, + "error": { + "team": { + "overSize": "Team member exceeds limit" + } + }, "file": { "Click to download file template": "Download Template: {{name}}", "Click to view file": "Click to view file", @@ -306,7 +338,7 @@ "Choice Models Desc": "Supports multiple models such as GPT, Claude, Spark, and ChatGLM", "Choice Open": "Open", "Choice Open Desc": "{{title}} follows the Apache License 2.0 open source protocol", - "Choice QA": "QA Struceture", + "Choice QA": "QA Structure", "Choice QA Desc": "The index is constructed with the structure of QA pairs, and ADAPTS to various scenarios such as Q&A and reading", "Choice Visual": "Visual workflow", "Choice Visual Desc": "Visualize modular operations, easily implement complex workflows, and make your AI no longer monolithic", @@ -316,7 +348,7 @@ "Dateset Desc": "", "Docs": "Docs", "FastGPT Ability": "{{title}} Ability", - "FastGPT Desc": "{{title}} is a knowledgebase question answering system based on LLM large language model, which provides out-of-the-box data processing, model invocation and other capabilities. At the same time, workflow orchestration can be performed through Flow visualization to achieve complex Q&A scenarios!", + "FastGPT Desc": "{{title}} is a dataset question answering system based on LLM large language model, which provides out-of-the-box data processing, model invocation and other capabilities. At the same time, workflow orchestration can be performed through Flow visualization to achieve complex Q&A scenarios!", "Features": "Features", "Footer Developer": "Developer", "Footer Docs": "Docs", @@ -365,13 +397,13 @@ "key tips": "You can use the API Key to access certain interfaces (you can't access the application, you need to use the API key within the application to access the application)." }, "outlink": { - "Copy Iframe": "Copy Iframe", + "Copy IFrame": "Copy IFrame", "Copy Link": "Copy", "Create API Key": "Create Key", "Create Link": "Create Link", "Delete Link": "Delete", "Edit API Key": "Edit Key", - "Edit Ifrme Link": "Edit Iframe Link", + "Edit IFrame Link": "Edit IFrame Link", "Edit Link": "Edit", "Edit Share Window": "Edit Share Window", "Link Name": "Link Name", @@ -385,6 +417,14 @@ "token auth Tips": "Identity verification server address. If this value is set, the server will be specified to send a request for identity verification before each session", "token auth use cases": "Review the authentication instructions" }, + "permission": { + "Private": "Private", + "Private Tip": "Be used only to oneself", + "Public": "Public", + "Public Tip": "Available to all team members", + "Set Private": "Set Private", + "Set Public": "Set to public" + }, "plugin": { "Confirm Delete": "Confirm to delete the plugin?", "Create Your Plugin": "Create Plugin", @@ -424,6 +464,7 @@ "OpenAI Account Setting": "OpenAI Account Setting", "Password": "Password", "Pay": "Pay", + "Permission": "Permission", "Personal Information": "Personal", "Promotion": "Promotion", "Promotion Rate": "Promotion Rate", @@ -434,12 +475,13 @@ "Set OpenAI Account Failed": "Set OpenAI account failed", "Sign Out": "Sign Out", "Source": "Source", + "Team": "Team", "Time": "Time", "Timezone": "Timezone", "Total Amount": "Total Amount", "Update Password": "Update Password", "Update password failed": "Update password failed", - "Update password succseful": "Update password succseful", + "Update password successful": "Update password successful", "Usage Record": "Usage", "apikey": { "key": "API Keys" @@ -447,6 +489,66 @@ "promotion": { "pay": "", "register": "" + }, + "team": { + "Balance": "Team Balance", + "Check Team": "Switch", + "Confirm Invite": "Confirm Invite", + "Create Team": "Create Team", + "Invite Member": "Invite", + "Invite Member Failed Tip": "Invite Member Failed", + "Invite Member Result Tip": "Invite Member Result", + "Invite Member Success Tip": "Invite member completed \n successful :{{success}} Person \n username invalid :{{inValid}}\n Already on team :{{inTeam}}", + "Invite Member Tips": "They can access or use other resources within your team", + "Invite Role Admin Alias": "Invite an Administrator to join", + "Invite Role Admin Tip": "Admin\nCan create, edit, and use team resources", + "Invite Role Visitor Alias": "Invite visitors to Join", + "Invite Role Visitor Tip": "Visitors\nCan only use resources and have no permission to create and edit them", + "Leave Team": "Leave", + "Leave Team Failed": "Leave Team Failed", + "Manage": "Team Manage", + "Member": "Member", + "Over Max Member Tip": "Team max {{max}} people", + "Personal Team": "Personal", + "Processing invitations": "Processing invitations", + "Processing invitations Tips": "You have {{amount}} of team invitations that need to be processed", + "Reinvite": "Reinvite", + "Remove Member Confirm Tip": "Confirm to move {{username}} off the team", + "Remove Member Tip": "Move out team", + "Role": "Role", + "Select Team": "Select Team", + "Set Name": "Team Name", + "Switch Team Failed": "Switch Team Failed", + "Team Name": "Team Name", + "Update Team": "Update Team", + "invite": { + "Accept Confirm": "Want to join the team?", + "Accepted": "Accepted", + "Deal Width Footer Tip": "It will automatically shut down after processing~", + "Reject": "Rejected", + "Reject Confirm": "Confirm to decline the invitation?", + "accept": "Accept", + "reject": "Reject" + }, + "member": { + "Confirm Leave": "Confirm leave the team?", + "active": "Accept", + "reject": "Rejected", + "waiting": "Waiting" + }, + "role": { + "Admin": "Admin", + "Member": "Member", + "Owner": "Owner", + "Update to Visitor": "Set to visitor", + "Visitor": "Visitor" + } + } + }, + "wallet": { + "bill": { + "Audio Speech": "Audio Speech", + "bill username": "User" } } } diff --git a/projects/app/public/locales/zh/common.json b/projects/app/public/locales/zh/common.json index 21ec5c4ba..282b5632e 100644 --- a/projects/app/public/locales/zh/common.json +++ b/projects/app/public/locales/zh/common.json @@ -45,6 +45,8 @@ "Open AI Advanced Settings": "高级配置", "Output Field Settings": "输出字段编辑", "Paste Config": "粘贴配置", + "To Chat": "前去对话", + "To Settings": "查看详情", "Variable Key Repeat Tip": "变量 key 重复", "module": { "Combine Modules": "组合模块", @@ -70,7 +72,7 @@ "Admin Mark Content": "纠正后的回复", "Complete Response": "完整响应", "Confirm to clear history": "确认清空该应用的在线聊天记录?分享和 API 调用的记录不会被清空。", - "Confirm to clear share chat histroy": "确认删除所有聊天记录?", + "Confirm to clear share chat history": "确认删除所有聊天记录?", "Exit Chat": "退出聊天", "Feedback Close": "关闭反馈", "Feedback Failed": "提交反馈异常", @@ -128,6 +130,7 @@ "Add": "添加", "Back": "返回", "Beta": "实验版", + "Business edition features": "这是商业版功能~", "Choose": "选择", "Close": "关闭", "Collect": "收藏", @@ -146,6 +149,7 @@ "Delete Success": "删除成功", "Delete Tip": "删除提示", "Delete Warning": "删除警告", + "Done": "完成", "Edit": "编辑", "Expired Time": "过期时间", "File": "文件", @@ -164,24 +168,29 @@ "Output": "输出", "Params": "参数", "Password inconsistency": "两次密码不一致", + "Please Input Name": "请输入名称", "Rename": "重命名", "Rename Failed": "重命名失败", "Rename Success": "重命名成功", "Request Error": "请求异常", + "Save Failed": "保存失败", + "Save Success": "保存成功", "Search": "搜索", "Select File Failed": "选择文件异常", "Select One Folder": "选择一个目录", "Set Avatar": "点击设置头像", "Set Name": "取个响亮的名字", "Status": "状态", + "Team": "团队", "Test": "测试", "Time": "时间", - "Unknow": "未知", - "Unknow Source": "未知来源", + "UnKnow": "未知", + "UnKnow Source": "未知来源", "Update Failed": "更新异常", "Update Success": "更新成功", "Update Successful": "更新成功", "Update Time": "更新时间", + "Username": "用户名", "error": { "unKnow": "出现了点意外~" }, @@ -191,6 +200,9 @@ "Move Success": "移动成功", "No Folder": "没有子目录了,就放这里吧", "Root Path": "根目录" + }, + "input": { + "Repeat Value": "有重复的值" } }, "core": { @@ -199,10 +211,23 @@ "Prompt": "提示词" }, "app": { - "Next Step Guide": "下一步指引" + "Next Step Guide": "下一步指引", + "Question Guide Tip": "对话结束后,会为生成 3 个引导性问题。", + "TTS": "语音播报", + "TTS Tip": "开启后,每次对话后可使用语音播放功能。使用该功能可能产生额外费用。", + "tts": { + "Close": "不使用", + "Model alloy": "女声 - Alloy", + "Model echo": "男声 - Echo", + "Test Listen": "试听", + "Test Listen Text": "你好,我是 FastGPT,有什么可以帮助你么?", + "Web": "浏览器自带(免费)" + } }, "chat": { - "Restart": "重开对话" + "Audio Speech Error": "语音播报异常", + "Restart": "重开对话", + "Send Message": "发送" }, "dataset": { "Choose Dataset": "关联知识库", @@ -262,6 +287,8 @@ "Select One Collection To Store": "选择一个文件进行存储" }, "data": { + "Can not delete tip": "无修改权限", + "Can not edit": "无编辑权限", "Delete Tip": "确认删除该条数据?", "File import": "文件导入", "Input Data": "导入新数据", @@ -272,6 +299,11 @@ "deleteDatasetTips": "确认删除该知识库?删除后数据无法恢复,请确认!", "deleteFolderTips": "确认删除该文件夹及其包含的所有知识库?删除后数据无法恢复,请确认!" }, + "error": { + "team": { + "overSize": "团队成员超出上限" + } + }, "file": { "Click to download file template": "点击下载模板:{{name}}", "Click to view file": "点击查看原始文件", @@ -365,13 +397,13 @@ "key tips": "你可以使用 API 秘钥访问一些特定的接口(无法访问应用,访问应用需使用应用内的API Key)" }, "outlink": { - "Copy Iframe": "嵌入网页", + "Copy IFrame": "嵌入网页", "Copy Link": "复制", "Create API Key": "创建新 Key", "Create Link": "创建链接", "Delete Link": "删除链接", "Edit API Key": "编辑 Key 信息", - "Edit Ifrme Link": "更新嵌入链接", + "Edit IFrame Link": "更新嵌入链接", "Edit Link": "编辑", "Edit Share Window": "更新分享窗口", "Link Name": "分享链接的名字", @@ -385,6 +417,14 @@ "token auth Tips": "身份校验服务器地址,如填写该值,每次对话前都会想指定服务器发送一个请求,进行身份校验", "token auth use cases": "查看身份验证使用说明" }, + "permission": { + "Private": "私有", + "Private Tip": "仅自己可用", + "Public": "团队", + "Public Tip": "团队所有成员可使用", + "Set Private": "设为私有", + "Set Public": "设为团队可用" + }, "plugin": { "Confirm Delete": "确认删除该插件?", "Create Your Plugin": "创建你的插件", @@ -417,13 +457,14 @@ "Change": "变更", "Copy invite url": "复制邀请链接", "Invite Url": "邀请链接", - "Invite url tip": "通过该链接注册的好友将永久与你绑定,其充值时你会获得一定余额奖励。\n此外,好友使用手机号注册时,你将立即获得 5 元奖励。", + "Invite url tip": "通过该链接注册的好友将永久与你绑定,其充值时你会获得一定余额奖励。\n此外,好友使用手机号注册时,你将立即获得 5 元奖励。\n奖励会发送到您的默认团队中。", "Language": "语言", "Notice": "通知", "Old password is error": "旧密码错误", "OpenAI Account Setting": "OpenAI 账号配置", "Password": "密码", "Pay": "充值", + "Permission": "使用权限", "Personal Information": "个人信息", "Promotion": "", "Promotion Rate": "返现比例", @@ -434,12 +475,13 @@ "Set OpenAI Account Failed": "设置 OpenAI 账号异常", "Sign Out": "登出", "Source": "来源", + "Team": "团队", "Time": "时间", "Timezone": "时区", "Total Amount": "总金额", "Update Password": "修改密码", "Update password failed": "修改密码异常", - "Update password succseful": "修改密码成功", + "Update password successful": "修改密码成功", "Usage Record": "使用记录", "apikey": { "key": "API 秘钥" @@ -447,6 +489,66 @@ "promotion": { "pay": "好友充值", "register": "好友注册" + }, + "team": { + "Balance": "团队余额", + "Check Team": "切换", + "Confirm Invite": "确认邀请", + "Create Team": "创建新团队", + "Invite Member": "邀请成员", + "Invite Member Failed Tip": "邀请成员出现异常", + "Invite Member Result Tip": "邀请结果提示", + "Invite Member Success Tip": "邀请成员完成\n成功: {{success}}人\n用户名无效: {{inValid}}\n已在团队中:{{inTeam}}", + "Invite Member Tips": "对方可查阅或使用团队内的其他资源", + "Invite Role Admin Alias": "邀请管理员加入", + "Invite Role Admin Tip": "管理员\n可创建、编辑和使用团队资源", + "Invite Role Visitor Alias": "邀请访客加入", + "Invite Role Visitor Tip": "访客\n仅可使用资源,无创建编辑权限", + "Leave Team": "离开团队", + "Leave Team Failed": "离开团队异常", + "Manage": "团队管理", + "Member": "成员", + "Over Max Member Tip": "团队最多{{max}}人", + "Personal Team": "个人团队", + "Processing invitations": "处理邀请", + "Processing invitations Tips": "你有{{amount}}个需要处理的团队邀请", + "Reinvite": "重新邀请", + "Remove Member Confirm Tip": "确认将 {{username}} 移出团队?", + "Remove Member Tip": "移出团队", + "Role": "身份", + "Select Team": "团队选择", + "Set Name": "给团队取个名字", + "Switch Team Failed": "切换团队异常", + "Team Name": "团队名", + "Update Team": "更新团队信息", + "invite": { + "Accept Confirm": "确认加入该团队?", + "Accepted": "已加入团队", + "Deal Width Footer Tip": "处理完会自动关闭噢~", + "Reject": "已拒绝邀请", + "Reject Confirm": "确认拒绝该邀请?", + "accept": "接受", + "reject": "拒绝" + }, + "member": { + "Confirm Leave": "确认离开该团队?", + "active": "已加入", + "reject": "拒绝", + "waiting": "待接受" + }, + "role": { + "Admin": "管理员", + "Member": "成员", + "Owner": "创建者", + "Update to Visitor": "修改成访客", + "Visitor": "访客" + } + } + }, + "wallet": { + "bill": { + "Audio Speech": "语音播报", + "bill username": "用户" } } } diff --git a/projects/app/src/components/Avatar/index.tsx b/projects/app/src/components/Avatar/index.tsx index 10fecc812..a849e940d 100644 --- a/projects/app/src/components/Avatar/index.tsx +++ b/projects/app/src/components/Avatar/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Image } from '@chakra-ui/react'; import type { ImageProps } from '@chakra-ui/react'; -import { LOGO_ICON } from '@/constants/chat'; +import { LOGO_ICON } from '@fastgpt/global/core/chat/constants'; const Avatar = ({ w = '30px', ...props }: ImageProps) => { return ( diff --git a/projects/app/src/components/ChatBox/ContextModal.tsx b/projects/app/src/components/ChatBox/ContextModal.tsx index 6802bdae4..1dd8d31e3 100644 --- a/projects/app/src/components/ChatBox/ContextModal.tsx +++ b/projects/app/src/components/ChatBox/ContextModal.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ModalBody, Box, useTheme } from '@chakra-ui/react'; -import { ChatItemType } from '@/types/chat'; +import { ChatItemType } from '@fastgpt/global/core/chat/type'; import MyModal from '../MyModal'; const ContextModal = ({ diff --git a/projects/app/src/components/ChatBox/QuoteModal.tsx b/projects/app/src/components/ChatBox/QuoteModal.tsx index 50c3f0db5..0c2c88ab9 100644 --- a/projects/app/src/components/ChatBox/QuoteModal.tsx +++ b/projects/app/src/components/ChatBox/QuoteModal.tsx @@ -16,6 +16,7 @@ import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/ty import MyTooltip from '../MyTooltip'; import NextLink from 'next/link'; import { useSystemStore } from '@/web/common/system/useSystemStore'; +import { useUserStore } from '@/web/support/user/useUserStore'; const QuoteModal = ({ rawSearch = [], @@ -28,14 +29,15 @@ const QuoteModal = ({ const { isPc } = useSystemStore(); const theme = useTheme(); const router = useRouter(); + const { userInfo } = useUserStore(); const { toast } = useToast(); const { setIsLoading, Loading } = useLoading(); - const [editInputData, setEditInputData] = useState(); + const [editInputData, setEditInputData] = useState(); const isShare = useMemo(() => router.pathname === '/chat/share', [router.pathname]); /** - * click edit, get new kbDataItem + * click edit, get new DataItem */ const onclickEdit = useCallback( async (item: InputDataType) => { @@ -181,6 +183,7 @@ const QuoteModal = ({ {editInputData && editInputData.id && ( setEditInputData(undefined)} onSuccess={() => { console.log('更新引用成功'); diff --git a/projects/app/src/components/ChatBox/ResponseTags.tsx b/projects/app/src/components/ChatBox/ResponseTags.tsx index 731250487..d695fe842 100644 --- a/projects/app/src/components/ChatBox/ResponseTags.tsx +++ b/projects/app/src/components/ChatBox/ResponseTags.tsx @@ -1,5 +1,6 @@ import React, { useMemo, useState } from 'react'; -import { ChatHistoryItemResType, ChatItemType } from '@/types/chat'; +import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d'; +import type { ChatItemType } from '@fastgpt/global/core/chat/type'; import { Flex, BoxProps, useDisclosure, Image, useTheme } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; import { useSystemStore } from '@/web/common/system/useSystemStore'; diff --git a/projects/app/src/components/ChatBox/SelectMarkCollection.tsx b/projects/app/src/components/ChatBox/SelectMarkCollection.tsx index 90cd46395..fca962490 100644 --- a/projects/app/src/components/ChatBox/SelectMarkCollection.tsx +++ b/projects/app/src/components/ChatBox/SelectMarkCollection.tsx @@ -6,7 +6,7 @@ import MyIcon from '@/components/Icon'; import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant'; import DatasetSelectModal, { useDatasetSelect } from '@/components/core/dataset/SelectModal'; import dynamic from 'next/dynamic'; -import { MarkDataType } from '@/global/core/dataset/type'; +import { AdminFbkType } from '@fastgpt/global/core/chat/type.d'; import SelectCollections from '@/web/core/dataset/components/SelectCollections'; const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal')); @@ -28,7 +28,7 @@ const SelectMarkCollection = ({ adminMarkData: AdminMarkType; setAdminMarkData: (e: AdminMarkType) => void; onClose: () => void; - onSuccess: (adminFeedback: MarkDataType) => void; + onSuccess: (adminFeedback: AdminFbkType) => void; }) => { const { t } = useTranslation(); const theme = useTheme(); @@ -166,12 +166,12 @@ const SelectMarkCollection = ({ datasetId={adminMarkData.datasetId} defaultValues={{ id: adminMarkData.dataId, - datasetId: adminMarkData.datasetId, collectionId: adminMarkData.collectionId, sourceName: '手动标注', q: adminMarkData.q, a: adminMarkData.a }} + canWrite onSuccess={(data) => { if (!data.q || !adminMarkData.datasetId || !adminMarkData.collectionId || !data.id) { return onClose(); diff --git a/projects/app/src/components/ChatBox/WholeResponseModal.tsx b/projects/app/src/components/ChatBox/WholeResponseModal.tsx index aa60225ce..c3f1ff8c7 100644 --- a/projects/app/src/components/ChatBox/WholeResponseModal.tsx +++ b/projects/app/src/components/ChatBox/WholeResponseModal.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useState } from 'react'; import { Box, useTheme, Flex, Image } from '@chakra-ui/react'; -import type { ChatHistoryItemResType } from '@/types/chat'; +import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d'; import { useTranslation } from 'react-i18next'; import { ModuleTemplatesFlat } from '@/constants/flow/ModuleTemplate'; import Tabs from '../Tabs'; @@ -8,7 +8,7 @@ import Tabs from '../Tabs'; import MyModal from '../MyModal'; import MyTooltip from '../MyTooltip'; import { QuestionOutlineIcon } from '@chakra-ui/icons'; -import { formatPrice } from '@fastgpt/global/common/bill/tools'; +import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; function Row({ label, value }: { label: string; value?: string | number | React.ReactNode }) { const theme = useTheme(); diff --git a/projects/app/src/components/ChatBox/index.tsx b/projects/app/src/components/ChatBox/index.tsx index dbe146ac0..71a087108 100644 --- a/projects/app/src/components/ChatBox/index.tsx +++ b/projects/app/src/components/ChatBox/index.tsx @@ -10,12 +10,9 @@ import React, { } from 'react'; import Script from 'next/script'; import { throttle } from 'lodash'; -import { - ChatHistoryItemResType, - ChatItemType, - ChatSiteItemType, - ExportChatType -} from '@/types/chat'; +import type { ExportChatType } from '@/types/chat.d'; +import type { ChatItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type.d'; +import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d'; import { useToast } from '@/web/common/hooks/useToast'; import { useAudioPlay } from '@/web/common/utils/voice'; import { getErrText } from '@fastgpt/global/common/error/utils'; @@ -38,12 +35,12 @@ import { useMarkdown } from '@/web/common/hooks/useMarkdown'; import { ModuleItemType } from '@fastgpt/global/core/module/type.d'; import { VariableInputEnum } from '@/constants/app'; import { useForm } from 'react-hook-form'; -import type { MessageItemType } from '@/types/core/chat/type'; +import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d'; import { fileDownload } from '@/web/common/file/utils'; import { htmlTemplate } from '@/constants/common'; import { useRouter } from 'next/router'; import { useSystemStore } from '@/web/common/system/useSystemStore'; -import { TaskResponseKeyEnum } from '@/constants/chat'; +import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; import { useTranslation } from 'react-i18next'; import { customAlphabet } from 'nanoid'; import { adminUpdateChatFeedback, userUpdateChatFeedback } from '@/web/core/chat/api'; @@ -64,6 +61,7 @@ const SelectMarkCollection = dynamic(() => import('./SelectMarkCollection')); import styles from './index.module.scss'; import { postQuestionGuide } from '@/web/core/ai/api'; import { splitGuideModule } from '@/global/core/app/modules/utils'; +import { AppTTSConfigType } from '@/types/app'; const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24); @@ -73,7 +71,7 @@ type generatingMessageProps = { text?: string; name?: string; status?: 'running' export type StartChatFnProps = { chatList: ChatSiteItemType[]; - messages: MessageItemType[]; + messages: ChatMessageItemType[]; controller: AbortController; variables: Record; generatingMessage: (e: generatingMessageProps) => void; @@ -158,7 +156,7 @@ const ChatBox = ( [chatHistory] ); - const { welcomeText, variableModules, questionGuide } = useMemo( + const { welcomeText, variableModules, questionGuide, ttsConfig } = useMemo( () => splitGuideModule(userGuideModule), [userGuideModule] ); @@ -206,32 +204,28 @@ const ChatBox = ( [] ); // eslint-disable-next-line react-hooks/exhaustive-deps - const generatingMessage = useCallback( - // concat text to end of message - ({ text = '', status, name }: generatingMessageProps) => { - setChatHistory((state) => - state.map((item, index) => { - if (index !== state.length - 1) return item; - return { - ...item, - ...(text - ? { - value: item.value + text - } - : {}), - ...(status && name - ? { - status, - moduleName: name - } - : {}) - }; - }) - ); - generatingScroll(); - }, - [generatingScroll, setChatHistory] - ); + const generatingMessage = ({ text = '', status, name }: generatingMessageProps) => { + setChatHistory((state) => + state.map((item, index) => { + if (index !== state.length - 1) return item; + return { + ...item, + ...(text + ? { + value: item.value + text + } + : {}), + ...(status && name + ? { + status, + moduleName: name + } + : {}) + }; + }) + ); + generatingScroll(); + }; // 重置输入内容 const resetInputVal = useCallback((val: string) => { @@ -489,7 +483,7 @@ const ChatBox = ( return { bg: colorMap[chatContent.status] || colorMap.loading, - name: t(chatContent.moduleName || 'common.Loading') + name: chatContent.moduleName || t('common.Loading') }; }, [chatHistory, isChatting, t]); /* style end */ @@ -654,8 +648,10 @@ const ChatBox = ( { @@ -801,13 +797,20 @@ const ChatBox = ( {/* message input */} {onStartChat && variableIsFinish && active ? ( - + @@ -836,6 +839,7 @@ const ChatBox = ( const textarea = e.target; textarea.style.height = textareaMinH; textarea.style.height = `${textarea.scrollHeight}px`; + setRefresh((state) => !state); }} onKeyDown={(e) => { // enter send.(pc or iframe && enter and unPress shift) @@ -852,11 +856,14 @@ const ChatBox = ( {isChatting ? ( chatController.current?.abort('stop')} /> ) : ( - { - handleSubmit((data) => sendPrompt(data, TextareaDom.current?.value))(); - }} - /> + + { + handleSubmit((data) => sendPrompt(data, TextareaDom.current?.value))(); + }} + /> + )} @@ -1106,8 +1115,10 @@ function Empty() { function ChatController({ chat, + setChatHistory, display, showVoiceIcon, + ttsConfig, onReadFeedback, onMark, onRetry, @@ -1117,7 +1128,9 @@ function ChatController({ mr }: { chat: ChatSiteItemType; + setChatHistory?: React.Dispatch>; showVoiceIcon?: boolean; + ttsConfig?: AppTTSConfigType; onRetry?: () => void; onDelete?: () => void; onMark?: () => void; @@ -1127,7 +1140,9 @@ function ChatController({ const theme = useTheme(); const { t } = useTranslation(); const { copyData } = useCopyData(); - const { audioLoading, audioPlaying, hasAudio, playAudio, cancelAudio } = useAudioPlay({}); + const { audioLoading, audioPlaying, hasAudio, playAudio, cancelAudio } = useAudioPlay({ + ttsConfig + }); const controlIconStyle = { w: '14px', cursor: 'pointer', @@ -1198,7 +1213,24 @@ function ChatController({ {...controlIconStyle} name={'voice'} _hover={{ color: '#E74694' }} - onClick={() => playAudio(chat.value)} + onClick={async () => { + const buffer = await playAudio({ + buffer: chat.ttsBuffer, + chatItemId: chat.dataId, + text: chat.value + }); + if (!setChatHistory) return; + setChatHistory((state) => + state.map((item) => + item.dataId === chat.dataId + ? { + ...item, + ttsBuffer: buffer + } + : item + ) + ); + }} /> ))} diff --git a/projects/app/src/components/Icon/icons/common/inviteLight.svg b/projects/app/src/components/Icon/icons/common/inviteLight.svg new file mode 100644 index 000000000..6d71ceb6b --- /dev/null +++ b/projects/app/src/components/Icon/icons/common/inviteLight.svg @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/projects/app/src/components/Icon/icons/common/playLight.svg b/projects/app/src/components/Icon/icons/common/playLight.svg new file mode 100644 index 000000000..539ad4690 --- /dev/null +++ b/projects/app/src/components/Icon/icons/common/playLight.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projects/app/src/components/Icon/icons/common/tickFill.svg b/projects/app/src/components/Icon/icons/common/tickFill.svg new file mode 100644 index 000000000..135a70c1e --- /dev/null +++ b/projects/app/src/components/Icon/icons/common/tickFill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projects/app/src/components/Icon/icons/core/app/ttsFill.svg b/projects/app/src/components/Icon/icons/core/app/ttsFill.svg new file mode 100644 index 000000000..d101f6628 --- /dev/null +++ b/projects/app/src/components/Icon/icons/core/app/ttsFill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projects/app/src/components/Icon/icons/core/chat/sendFill.svg b/projects/app/src/components/Icon/icons/core/chat/sendFill.svg new file mode 100644 index 000000000..5fb488bd9 --- /dev/null +++ b/projects/app/src/components/Icon/icons/core/chat/sendFill.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/projects/app/src/components/Icon/icons/fill/play.svg b/projects/app/src/components/Icon/icons/fill/play.svg index a985e1353..4a7ce1b16 100644 --- a/projects/app/src/components/Icon/icons/fill/play.svg +++ b/projects/app/src/components/Icon/icons/fill/play.svg @@ -1 +1,8 @@ - \ No newline at end of file + + + + \ No newline at end of file diff --git a/projects/app/src/components/Icon/icons/support/permission/privateLight.svg b/projects/app/src/components/Icon/icons/support/permission/privateLight.svg new file mode 100644 index 000000000..a72b6f44a --- /dev/null +++ b/projects/app/src/components/Icon/icons/support/permission/privateLight.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/projects/app/src/components/Icon/icons/support/permission/publicLight.svg b/projects/app/src/components/Icon/icons/support/permission/publicLight.svg new file mode 100644 index 000000000..dd26e5a7b --- /dev/null +++ b/projects/app/src/components/Icon/icons/support/permission/publicLight.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projects/app/src/components/Icon/icons/support/team/memberLight.svg b/projects/app/src/components/Icon/icons/support/team/memberLight.svg new file mode 100644 index 000000000..5b81b3474 --- /dev/null +++ b/projects/app/src/components/Icon/icons/support/team/memberLight.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projects/app/src/components/Icon/index.tsx b/projects/app/src/components/Icon/index.tsx index 11dc05a68..1cdde2733 100644 --- a/projects/app/src/components/Icon/index.tsx +++ b/projects/app/src/components/Icon/index.tsx @@ -98,7 +98,15 @@ const iconPaths = { 'common/refreshLight': () => import('./icons/common/refreshLight.svg'), 'core/module/previewLight': () => import('./icons/core/module/previewLight.svg'), 'core/chat/quoteFill': () => import('./icons/core/chat/quoteFill.svg'), - 'core/chat/QGFill': () => import('./icons/core/chat/QGFill.svg') + 'core/chat/QGFill': () => import('./icons/core/chat/QGFill.svg'), + 'common/tickFill': () => import('./icons/common/tickFill.svg'), + 'common/inviteLight': () => import('./icons/common/inviteLight.svg'), + 'support/team/memberLight': () => import('./icons/support/team/memberLight.svg'), + 'support/permission/privateLight': () => import('./icons/support/permission/privateLight.svg'), + 'support/permission/publicLight': () => import('./icons/support/permission/publicLight.svg'), + 'core/app/ttsFill': () => import('./icons/core/app/ttsFill.svg'), + 'common/playLight': () => import('./icons/common/playLight.svg'), + 'core/chat/sendFill': () => import('./icons/core/chat/sendFill.svg') }; export type IconName = keyof typeof iconPaths; diff --git a/projects/app/src/components/Layout/index.tsx b/projects/app/src/components/Layout/index.tsx index 00e072c0d..767bbd3e3 100644 --- a/projects/app/src/components/Layout/index.tsx +++ b/projects/app/src/components/Layout/index.tsx @@ -4,12 +4,19 @@ import { useRouter } from 'next/router'; import { useLoading } from '@/web/common/hooks/useLoading'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { throttle } from 'lodash'; +import { useQuery } from '@tanstack/react-query'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import { getUnreadCount } from '@/web/support/user/inform/api'; +import { feConfigs } from '@/web/common/system/staticData'; +import dynamic from 'next/dynamic'; + import Auth from './auth'; import Navbar from './navbar'; import NavbarPhone from './navbarPhone'; -import { useQuery } from '@tanstack/react-query'; -import { useUserStore } from '@/web/support/user/useUserStore'; -import { getUnreadCount } from '@/web/support/user/api'; +const UpdateInviteModal = dynamic( + () => import('@/components/support/user/team/UpdateInviteModal'), + { ssr: false } +); const pcUnShowLayoutRoute: Record = { '/': true, @@ -60,7 +67,7 @@ const Layout = ({ children }: { children: JSX.Element }) => { }, [loadGitStar, setScreenWidth]); const { data: unread = 0 } = useQuery(['getUnreadCount'], getUnreadCount, { - enabled: !!userInfo, + enabled: !!userInfo && feConfigs.isPlus, refetchInterval: 10000 }); @@ -103,6 +110,7 @@ const Layout = ({ children }: { children: JSX.Element }) => { )} + {!!userInfo && } ); }; diff --git a/projects/app/src/components/Layout/navbar.tsx b/projects/app/src/components/Layout/navbar.tsx index 8a227ece8..3d0f61247 100644 --- a/projects/app/src/components/Layout/navbar.tsx +++ b/projects/app/src/components/Layout/navbar.tsx @@ -3,7 +3,7 @@ import { Box, Flex, Link } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import { useUserStore } from '@/web/support/user/useUserStore'; import { useChatStore } from '@/web/core/chat/storeChat'; -import { HUMAN_ICON } from '@/constants/chat'; +import { HUMAN_ICON } from '@fastgpt/global/core/chat/constants'; import { feConfigs } from '@/web/common/system/staticData'; import NextLink from 'next/link'; import Badge from '../Badge'; diff --git a/projects/app/src/components/Markdown/chat/Quote.tsx b/projects/app/src/components/Markdown/chat/Quote.tsx index 3f69fc412..e0721bd37 100644 --- a/projects/app/src/components/Markdown/chat/Quote.tsx +++ b/projects/app/src/components/Markdown/chat/Quote.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; import { Box, useTheme } from '@chakra-ui/react'; -import { getFileAndOpen } from '@/web/common/file/utils'; +import { getFileAndOpen } from '@/web/core/dataset/utils'; import { useToast } from '@/web/common/hooks/useToast'; import { getErrText } from '@fastgpt/global/common/error/utils'; diff --git a/projects/app/src/components/Markdown/img/MermaidCodeBlock.tsx b/projects/app/src/components/Markdown/img/MermaidCodeBlock.tsx index 56e087f86..65fbc206e 100644 --- a/projects/app/src/components/Markdown/img/MermaidCodeBlock.tsx +++ b/projects/app/src/components/Markdown/img/MermaidCodeBlock.tsx @@ -91,7 +91,7 @@ const MermaidBlock = ({ code }: { code: string }) => { a.download = 'mermaid.jpg'; document.body.appendChild(a); a.click(); - document.body.removeChild(a); + document.body?.removeChild(a); }; img.onerror = (e) => { console.log(e); diff --git a/projects/app/src/components/MyMenu/index.tsx b/projects/app/src/components/MyMenu/index.tsx index f3a33ad93..bda2ae126 100644 --- a/projects/app/src/components/MyMenu/index.tsx +++ b/projects/app/src/components/MyMenu/index.tsx @@ -41,7 +41,8 @@ const MyMenu = ({ width, offset = [0, 10], Button, menuList }: Props) => { e.stopPropagation(); item.onClick && item.onClick(); }} - color={item.isActive ? 'hover.blue' : ''} + color={item.isActive ? 'myBlue.600' : ''} + whiteSpace={'pre-wrap'} > {item.child} diff --git a/projects/app/src/components/MyTooltip/index.tsx b/projects/app/src/components/MyTooltip/index.tsx index 208403cb4..8c25d9216 100644 --- a/projects/app/src/components/MyTooltip/index.tsx +++ b/projects/app/src/components/MyTooltip/index.tsx @@ -10,6 +10,7 @@ const MyTooltip = ({ children, forceShow = false, shouldWrapChildren = true, ... const { isPc } = useSystemStore(); return isPc || forceShow ? ( onChange(item.value)} > {!!item.icon && } - + {item.title} {!!item.desc && ( diff --git a/projects/app/src/components/Select/index.tsx b/projects/app/src/components/Select/index.tsx index 0008d780b..81591933f 100644 --- a/projects/app/src/components/Select/index.tsx +++ b/projects/app/src/components/Select/index.tsx @@ -1,4 +1,4 @@ -import React, { useRef, forwardRef } from 'react'; +import React, { useRef, forwardRef, useMemo } from 'react'; import { Menu, Box, @@ -14,10 +14,11 @@ interface Props extends ButtonProps { value?: string; placeholder?: string; list: { - label: string; + alias?: string; + label: string | React.ReactNode; value: string; }[]; - onchange?: (val: string) => void; + onchange?: (val: any) => void; } const MySelect = ( @@ -36,6 +37,7 @@ const MySelect = ( } }; const { isOpen, onOpen, onClose } = useDisclosure(); + const selectItem = useMemo(() => list.find((item) => item.value === value), [list, value]); useOutsideClick({ ref: SelectRef, @@ -72,7 +74,7 @@ const MySelect = ( : {})} {...props} > - {list.find((item) => item.value === value)?.label || placeholder} + {selectItem?.alias || selectItem?.label || placeholder} @@ -103,7 +105,8 @@ const MySelect = ( {...menuItemStyles} {...(value === item.value ? { - color: 'myBlue.600' + color: 'myBlue.600', + bg: 'myWhite.300' } : {})} onClick={() => { @@ -111,6 +114,7 @@ const MySelect = ( onchange(item.value); } }} + whiteSpace={'pre-wrap'} > {item.label} diff --git a/projects/app/src/components/common/Textarea/TagTextarea.tsx b/projects/app/src/components/common/Textarea/TagTextarea.tsx new file mode 100644 index 000000000..4313b3de4 --- /dev/null +++ b/projects/app/src/components/common/Textarea/TagTextarea.tsx @@ -0,0 +1,99 @@ +import React, { useCallback, useRef, useState } from 'react'; +import { + Box, + BoxProps, + Flex, + Input, + Tag, + TagCloseButton, + TagLabel, + useTheme +} from '@chakra-ui/react'; +import { useToast } from '@/web/common/hooks/useToast'; +import { useTranslation } from 'react-i18next'; + +type Props = BoxProps & { defaultValues: string[]; onUpdate: (e: string[]) => void }; + +const TagTextarea = ({ defaultValues, onUpdate, ...props }: Props) => { + const theme = useTheme(); + const InputRef = useRef(null); + const { t } = useTranslation(); + const { toast } = useToast(); + const [focus, setFocus] = useState(false); + const [tags, setTags] = useState(defaultValues); + + const onUpdateValue = useCallback( + (value?: string) => { + setFocus(false); + if (!value || !InputRef.current?.value) { + return; + } + if (tags.includes(value)) { + toast({ + status: 'warning', + title: t('common.input.Repeat Value') + }); + } + setTags([...tags, value]); + onUpdate([...tags, value]); + InputRef.current.value = ''; + }, + [onUpdate, t, tags, toast] + ); + + return ( + { + if (!focus) { + InputRef.current?.focus(); + setFocus(true); + } + }} + > + + {tags.map((tag, i) => ( + e.stopPropagation()}> + {tag} + { + const val = tags.filter((_, index) => index !== i); + setTags(val); + onUpdate(val); + }} + /> + + ))} + { + const value = e.target.value; + onUpdateValue(value); + }} + onKeyDown={(e) => { + if (e.keyCode === 13) { + e.preventDefault(); + onUpdateValue(InputRef.current?.value); + } + }} + /> + + + ); +}; + +export default TagTextarea; diff --git a/projects/app/src/components/core/module/AIChatSettingsModal.tsx b/projects/app/src/components/core/module/AIChatSettingsModal.tsx index 79910cf9e..195547f78 100644 --- a/projects/app/src/components/core/module/AIChatSettingsModal.tsx +++ b/projects/app/src/components/core/module/AIChatSettingsModal.tsx @@ -49,7 +49,7 @@ const AIChatSettingsModal = ({ }>(); const tokenLimit = useMemo(() => { - return chatModelList.find((item) => item.model === getValues('model'))?.maxToken || 4000; + return chatModelList.find((item) => item.model === getValues('model'))?.maxResponse || 4000; }, [getValues, refresh]); const LabelStyles: BoxProps = { diff --git a/projects/app/src/components/core/module/Flow/ChatTest.tsx b/projects/app/src/components/core/module/Flow/ChatTest.tsx index b3ca7a23a..cfe46f400 100644 --- a/projects/app/src/components/core/module/Flow/ChatTest.tsx +++ b/projects/app/src/components/core/module/Flow/ChatTest.tsx @@ -1,5 +1,5 @@ import type { ModuleItemType } from '@fastgpt/global/core/module/type.d'; -import { AppSchema } from '@/types/mongoSchema'; +import { AppSchema } from '@fastgpt/global/core/app/type.d'; import React, { useMemo, useCallback, @@ -47,7 +47,7 @@ const ChatTest = ( // 流请求,获取数据 const { responseText, responseData } = await streamFetch({ - url: '/api/chat/chatTest', + url: '/api/core/chat/chatTest', data: { history, prompt: chatList[chatList.length - 2].value, diff --git a/projects/app/src/components/core/module/Flow/SelectAppModal.tsx b/projects/app/src/components/core/module/Flow/SelectAppModal.tsx index 953b23bd8..597f15ee6 100644 --- a/projects/app/src/components/core/module/Flow/SelectAppModal.tsx +++ b/projects/app/src/components/core/module/Flow/SelectAppModal.tsx @@ -7,6 +7,7 @@ import Avatar from '@/components/Avatar'; import { useTranslation } from 'react-i18next'; import { useLoading } from '@/web/common/hooks/useLoading'; import { useUserStore } from '@/web/support/user/useUserStore'; +import { useAppStore } from '@/web/core/app/store/useAppStore'; const SelectAppModal = ({ defaultApps = [], @@ -26,7 +27,7 @@ const SelectAppModal = ({ const theme = useTheme(); const [selectedApps, setSelectedApps] = React.useState(defaultApps); /* 加载模型 */ - const { myApps, loadMyApps } = useUserStore(); + const { myApps, loadMyApps } = useAppStore(); const { isLoading } = useQuery(['loadMyApos'], () => loadMyApps()); const apps = useMemo( diff --git a/projects/app/src/components/core/module/Flow/components/modules/ExtractFieldModal.tsx b/projects/app/src/components/core/module/Flow/components/modules/ExtractFieldModal.tsx index 66f170c55..1df7271c1 100644 --- a/projects/app/src/components/core/module/Flow/components/modules/ExtractFieldModal.tsx +++ b/projects/app/src/components/core/module/Flow/components/modules/ExtractFieldModal.tsx @@ -9,7 +9,7 @@ import { Switch, Input } from '@chakra-ui/react'; -import type { ContextExtractAgentItemType } from '@/types/app'; +import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type'; import { useForm } from 'react-hook-form'; import { customAlphabet } from 'nanoid'; const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6); diff --git a/projects/app/src/components/core/module/Flow/components/nodes/NodeCQNode.tsx b/projects/app/src/components/core/module/Flow/components/nodes/NodeCQNode.tsx index a2ef86cd8..bf217d322 100644 --- a/projects/app/src/components/core/module/Flow/components/nodes/NodeCQNode.tsx +++ b/projects/app/src/components/core/module/Flow/components/nodes/NodeCQNode.tsx @@ -6,7 +6,7 @@ import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d'; import Divider from '../modules/Divider'; import Container from '../modules/Container'; import RenderInput from '../render/RenderInput'; -import type { ClassifyQuestionAgentItemType } from '@/types/app'; +import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/module/type.d'; import { customAlphabet } from 'nanoid'; const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4); import MyIcon from '@/components/Icon'; diff --git a/projects/app/src/components/core/module/Flow/components/nodes/NodeExtract.tsx b/projects/app/src/components/core/module/Flow/components/nodes/NodeExtract.tsx index 190ce1ad8..f759b6ef0 100644 --- a/projects/app/src/components/core/module/Flow/components/nodes/NodeExtract.tsx +++ b/projects/app/src/components/core/module/Flow/components/nodes/NodeExtract.tsx @@ -8,7 +8,7 @@ import Container from '../modules/Container'; import { AddIcon } from '@chakra-ui/icons'; import RenderInput from '../render/RenderInput'; import Divider from '../modules/Divider'; -import { ContextExtractAgentItemType } from '@/types/app'; +import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type'; import RenderOutput from '../render/RenderOutput'; import MyIcon from '@/components/Icon'; import ExtractFieldModal from '../modules/ExtractFieldModal'; diff --git a/projects/app/src/components/core/module/Flow/components/nodes/NodeUserGuide.tsx b/projects/app/src/components/core/module/Flow/components/nodes/NodeUserGuide.tsx index 6b9972bb0..ef638ed9a 100644 --- a/projects/app/src/components/core/module/Flow/components/nodes/NodeUserGuide.tsx +++ b/projects/app/src/components/core/module/Flow/components/nodes/NodeUserGuide.tsx @@ -15,9 +15,9 @@ import { Switch } from '@chakra-ui/react'; import { QuestionOutlineIcon } from '@chakra-ui/icons'; -import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d'; +import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type.d'; import { SystemInputEnum } from '@/constants/app'; -import { welcomeTextTip, variableTip, questionGuideTip } from '@/constants/flow/ModuleTemplate'; +import { welcomeTextTip, variableTip } from '@/constants/flow/ModuleTemplate'; import { onChangeNode } from '../../FlowProvider'; import VariableEditModal, { addVariable } from '../../../VariableEditModal'; @@ -26,18 +26,24 @@ import MyTooltip from '@/components/MyTooltip'; import Container from '../modules/Container'; import NodeCard from '../modules/NodeCard'; import { VariableItemType } from '@/types/app'; +import QGSwitch from '@/pages/app/detail/components/QGSwitch'; +import TTSSelect from '@/pages/app/detail/components/TTSSelect'; +import { splitGuideModule } from '@/global/core/app/modules/utils'; const NodeUserGuide = ({ data }: NodeProps) => { const theme = useTheme(); return ( <> - + + + + @@ -215,29 +221,43 @@ function QuestionGuide({ data }: { data: FlowModuleItemType }) { ); return ( - - - 下一步指引 - - - - - { - const value = e.target.checked; - onChangeNode({ - moduleId, - key: SystemInputEnum.questionGuide, - type: 'updateInput', - value: { - ...inputs.find((item) => item.key === SystemInputEnum.questionGuide), - value - } - }); - }} - /> - + { + const value = e.target.checked; + onChangeNode({ + moduleId, + key: SystemInputEnum.questionGuide, + type: 'updateInput', + value: { + ...inputs.find((item) => item.key === SystemInputEnum.questionGuide), + value + } + }); + }} + /> + ); +} + +function TTSGuide({ data }: { data: FlowModuleItemType }) { + const { inputs, moduleId } = data; + const { ttsConfig } = splitGuideModule({ inputs } as ModuleItemType); + + return ( + { + onChangeNode({ + moduleId, + key: SystemInputEnum.tts, + type: 'updateInput', + value: { + ...inputs.find((item) => item.key === SystemInputEnum.tts), + value: e + } + }); + }} + /> ); } diff --git a/projects/app/src/components/core/module/Flow/components/render/RenderInput.tsx b/projects/app/src/components/core/module/Flow/components/render/RenderInput.tsx index 358cf46fc..5f92576ab 100644 --- a/projects/app/src/components/core/module/Flow/components/render/RenderInput.tsx +++ b/projects/app/src/components/core/module/Flow/components/render/RenderInput.tsx @@ -29,11 +29,11 @@ import MyIcon from '@/components/Icon'; import { useTranslation } from 'react-i18next'; import { AIChatProps } from '@/types/core/aiChat'; import { chatModelList } from '@/web/common/system/staticData'; -import { formatPrice } from '@fastgpt/global/common/bill/tools'; +import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; import { useDatasetStore } from '@/web/core/dataset/store/dataset'; import { SelectedDatasetType } from '@/types/core/dataset'; import { useQuery } from '@tanstack/react-query'; -import { LLMModelItemType } from '@/types/model'; +import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d'; import type { EditFieldModeType, EditFieldType } from '../modules/FieldEditModal'; const FieldEditModal = dynamic(() => import('../modules/FieldEditModal')); @@ -406,7 +406,7 @@ var MaxTokenRender = React.memo(function MaxTokenRender({ }: RenderProps) { const model = inputs.find((item) => item.key === 'model')?.value; const modelData = chatModelList.find((item) => item.model === model); - const maxToken = modelData ? modelData.maxToken : 4000; + const maxToken = modelData ? modelData.maxResponse : 4000; const markList = [ { label: '100', value: 100 }, { label: `${maxToken}`, value: maxToken } @@ -468,10 +468,10 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({ ...inputs.find((input) => input.key === 'maxToken'), markList: [ { label: '100', value: 100 }, - { label: `${model.maxToken}`, value: model.maxToken } + { label: `${model.maxResponse}`, value: model.maxResponse } ], - max: model.maxToken, - value: model.maxToken / 2 + max: model.maxResponse, + value: model.maxResponse / 2 } }); } diff --git a/projects/app/src/components/support/apikey/Table.tsx b/projects/app/src/components/support/apikey/Table.tsx index 255fecb64..ae35119fe 100644 --- a/projects/app/src/components/support/apikey/Table.tsx +++ b/projects/app/src/components/support/apikey/Table.tsx @@ -26,7 +26,7 @@ import { delOpenApiById, putOpenApiKey } from '@/web/support/openapi/api'; -import type { EditApiKeyProps } from '@/global/support/api/openapiReq'; +import type { EditApiKeyProps } from '@/global/support/openapi/api.d'; import { useQuery, useMutation } from '@tanstack/react-query'; import { useLoading } from '@/web/common/hooks/useLoading'; import dayjs from 'dayjs'; diff --git a/projects/app/src/components/support/permission/IconText/index.tsx b/projects/app/src/components/support/permission/IconText/index.tsx new file mode 100644 index 000000000..515fba31e --- /dev/null +++ b/projects/app/src/components/support/permission/IconText/index.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { PermissionTypeEnum, PermissionTypeMap } from '@fastgpt/global/support/permission/constant'; +import { Box, Flex, FlexProps } from '@chakra-ui/react'; +import MyIcon from '@/components/Icon'; +import { useTranslation } from 'react-i18next'; + +const PermissionIconText = ({ + permission, + ...props +}: { permission: `${PermissionTypeEnum}` } & FlexProps) => { + const { t } = useTranslation(); + return PermissionTypeMap[permission] ? ( + + + {t(PermissionTypeMap[permission]?.label)} + + ) : null; +}; + +export default PermissionIconText; diff --git a/projects/app/src/components/support/permission/Radio/index.tsx b/projects/app/src/components/support/permission/Radio/index.tsx new file mode 100644 index 000000000..2c86e5973 --- /dev/null +++ b/projects/app/src/components/support/permission/Radio/index.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import MyRadio from '@/components/Radio'; +import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { useTranslation } from 'react-i18next'; + +const PermissionRadio = ({ + value, + onChange +}: { + value: `${PermissionTypeEnum}`; + onChange: (e: `${PermissionTypeEnum}`) => void; +}) => { + const { t } = useTranslation(); + + return ( + onChange(e as `${PermissionTypeEnum}`)} + /> + ); +}; + +export default PermissionRadio; diff --git a/projects/app/src/components/support/user/team/TeamManageModal/EditModal.tsx b/projects/app/src/components/support/user/team/TeamManageModal/EditModal.tsx new file mode 100644 index 000000000..0ba2f7ea8 --- /dev/null +++ b/projects/app/src/components/support/user/team/TeamManageModal/EditModal.tsx @@ -0,0 +1,159 @@ +import React, { useCallback, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; +import { compressImgAndUpload } from '@/web/common/file/controller'; +import { useToast } from '@/web/common/hooks/useToast'; +import { getErrText } from '@fastgpt/global/common/error/utils'; +import { useRequest } from '@/web/common/hooks/useRequest'; +import MyModal from '@/components/MyModal'; +import { Box, Button, Flex, Input, ModalBody, ModalFooter } from '@chakra-ui/react'; +import MyTooltip from '@/components/MyTooltip'; +import Avatar from '@/components/Avatar'; +import { postCreateTeam, putUpdateTeam } from '@/web/support/user/team/api'; +import { CreateTeamProps } from '@fastgpt/global/support/user/team/controller.d'; + +export type FormDataType = CreateTeamProps & { + id?: string; +}; + +export const defaultForm = { + name: '', + avatar: '/icon/logo.svg' +}; + +function EditModal({ + defaultData = defaultForm, + onClose, + onSuccess +}: { + defaultData?: FormDataType; + onClose: () => void; + onSuccess: () => void; +}) { + const { t } = useTranslation(); + const [refresh, setRefresh] = useState(false); + const { toast } = useToast(); + + const { register, setValue, getValues, handleSubmit } = useForm({ + defaultValues: defaultData + }); + + const { File, onOpen: onOpenSelectFile } = useSelectFile({ + fileType: '.jpg,.png,.svg', + multiple: false + }); + + const onSelectFile = useCallback( + async (e: File[]) => { + const file = e[0]; + if (!file) return; + try { + const src = await compressImgAndUpload({ + file, + maxW: 100, + maxH: 100 + }); + setValue('avatar', src); + setRefresh((state) => !state); + } catch (err: any) { + toast({ + title: getErrText(err, t('common.Select File Failed')), + status: 'warning' + }); + } + }, + [setValue, t, toast] + ); + + const { mutate: onclickCreate, isLoading: creating } = useRequest({ + mutationFn: async (data: CreateTeamProps) => { + return postCreateTeam(data); + }, + onSuccess() { + onSuccess(); + onClose(); + }, + successToast: t('common.Create Success'), + errorToast: t('common.Create Failed') + }); + const { mutate: onclickUpdate, isLoading: updating } = useRequest({ + mutationFn: async (data: FormDataType) => { + if (!data.id) return Promise.resolve(''); + return putUpdateTeam({ + teamId: data.id, + name: data.name, + avatar: data.avatar + }); + }, + onSuccess() { + onSuccess(); + onClose(); + }, + successToast: t('common.Update Success'), + errorToast: t('common.Update Failed') + }); + + return ( + + + + {t('user.team.Set Name')} + + + + + + + + + + + {!!defaultData.id ? ( + <> + + + + + ) : ( + + )} + + + + ); +} + +export default React.memo(EditModal); diff --git a/projects/app/src/components/support/user/team/TeamManageModal/InviteModal.tsx b/projects/app/src/components/support/user/team/TeamManageModal/InviteModal.tsx new file mode 100644 index 000000000..a0085dfcf --- /dev/null +++ b/projects/app/src/components/support/user/team/TeamManageModal/InviteModal.tsx @@ -0,0 +1,106 @@ +import React, { useMemo, useState } from 'react'; +import MyModal from '@/components/MyModal'; +import { useTranslation } from 'react-i18next'; +import { ModalCloseButton, ModalBody, Box, ModalFooter, Button } from '@chakra-ui/react'; +import TagTextarea from '@/components/common/Textarea/TagTextarea'; +import MySelect from '@/components/Select'; +import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; +import { useRequest } from '@/web/common/hooks/useRequest'; +import { postInviteTeamMember } from '@/web/support/user/team/api'; +import { useConfirm } from '@/web/common/hooks/useConfirm'; +import type { InviteMemberResponse } from '@fastgpt/global/support/user/team/controller.d'; + +const InviteModal = ({ + teamId, + onClose, + onSuccess +}: { + teamId: string; + onClose: () => void; + onSuccess: () => void; +}) => { + const { t } = useTranslation(); + const { ConfirmModal, openConfirm } = useConfirm({ + title: t('user.team.Invite Member Result Tip'), + showCancel: false + }); + const [inviteUsernames, setInviteUsernames] = useState([]); + const inviteTypes = useMemo( + () => [ + { + alias: t('user.team.Invite Role Visitor Alias'), + label: t('user.team.Invite Role Visitor Tip'), + value: TeamMemberRoleEnum.visitor + }, + { + alias: t('user.team.Invite Role Admin Alias'), + label: t('user.team.Invite Role Admin Tip'), + value: TeamMemberRoleEnum.admin + } + ], + [t] + ); + const [selectedInviteType, setSelectInviteType] = useState(inviteTypes[0].value); + + const { mutate: onInvite, isLoading } = useRequest({ + mutationFn: () => { + return postInviteTeamMember({ + teamId, + usernames: inviteUsernames, + role: selectedInviteType + }); + }, + onSuccess(res: InviteMemberResponse) { + onSuccess(); + openConfirm( + () => onClose(), + undefined, + t('user.team.Invite Member Success Tip', { + success: res.invite.length, + inValid: res.inValid.join(', '), + inTeam: res.inTeam.join(', ') + }) + )(); + }, + errorToast: t('user.team.Invite Member Failed Tip') + }); + + return ( + + {t('user.team.Invite Member')} + + {t('user.team.Invite Member Tips')} + + + } + maxW={['90vw', '400px']} + overflow={'unset'} + > + + + {t('common.Username')} + + + + + + + + + + + ); +}; + +export default InviteModal; diff --git a/projects/app/src/components/support/user/team/TeamManageModal/index.tsx b/projects/app/src/components/support/user/team/TeamManageModal/index.tsx new file mode 100644 index 000000000..35d3e85dc --- /dev/null +++ b/projects/app/src/components/support/user/team/TeamManageModal/index.tsx @@ -0,0 +1,436 @@ +import React, { useMemo, useState } from 'react'; +import MyModal from '@/components/MyModal'; +import { useTranslation } from 'react-i18next'; +import { useQuery } from '@tanstack/react-query'; +import { + getTeamList, + getTeamMembers, + putSwitchTeam, + putUpdateMember, + delRemoveMember, + delLeaveTeam +} from '@/web/support/user/team/api'; +import { + Box, + Button, + Flex, + IconButton, + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + useTheme, + useDisclosure, + MenuButton +} from '@chakra-ui/react'; +import MyIcon from '@/components/Icon'; +import Avatar from '@/components/Avatar'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import { + TeamMemberRoleEnum, + TeamMemberRoleMap, + TeamMemberStatusEnum, + TeamMemberStatusMap +} from '@fastgpt/global/support/user/team/constant'; +import dynamic from 'next/dynamic'; +import { useRequest } from '@/web/common/hooks/useRequest'; +import { setToken } from '@/web/support/user/auth'; +import { useLoading } from '@/web/common/hooks/useLoading'; +import { FormDataType, defaultForm } from './EditModal'; +import MyMenu from '@/components/MyMenu'; +import { useConfirm } from '@/web/common/hooks/useConfirm'; +import { useToast } from '@/web/common/hooks/useToast'; + +const EditModal = dynamic(() => import('./EditModal')); +const InviteModal = dynamic(() => import('./InviteModal')); + +const TeamManageModal = ({ onClose }: { onClose: () => void }) => { + const theme = useTheme(); + const { t } = useTranslation(); + const { Loading } = useLoading(); + const { toast } = useToast(); + + const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm(); + const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({ + content: t('user.team.member.Confirm Leave') + }); + + const { userInfo, initUserInfo } = useUserStore(); + const [editTeamData, setEditTeamData] = useState(); + const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure(); + + const { + data: myTeams = [], + isLoading: isLoadingTeams, + refetch: refetchTeam + } = useQuery(['getTeams', userInfo?._id], () => getTeamList(TeamMemberStatusEnum.active)); + const defaultTeam = useMemo( + () => myTeams.find((item) => item.defaultTeam) || myTeams[0], + [myTeams] + ); + + const { mutate: onSwitchTeam, isLoading: isSwitchTeam } = useRequest({ + mutationFn: async (teamId: string) => { + const token = await putSwitchTeam(teamId); + setToken(token); + return initUserInfo(); + }, + errorToast: t('user.team.Switch Team Failed') + }); + + // member action + const { data: members = [], refetch: refetchMembers } = useQuery( + ['getMembers', userInfo?.team?.teamId], + () => { + if (!userInfo?.team?.teamId) return []; + return getTeamMembers(userInfo.team.teamId); + } + ); + + const { mutate: onUpdateMember, isLoading: isLoadingUpdateMember } = useRequest({ + mutationFn: putUpdateMember, + onSuccess() { + refetchMembers(); + } + }); + const { mutate: onRemoveMember, isLoading: isLoadingRemoveMember } = useRequest({ + mutationFn: delRemoveMember, + onSuccess() { + refetchMembers(); + } + }); + const { mutate: onLeaveTeam, isLoading: isLoadingLeaveTeam } = useRequest({ + mutationFn: async (teamId?: string) => { + if (!teamId) return; + // change to personal team + await onSwitchTeam(defaultTeam.teamId); + return delLeaveTeam(teamId); + }, + onSuccess() { + refetchTeam(); + }, + errorToast: t('user.team.Leave Team Failed') + }); + + return !!userInfo?.team ? ( + <> + + + {/* teams */} + + + + {t('common.Team')} + + {myTeams.length < 1 && ( + + } + aria-label={''} + onClick={() => setEditTeamData(defaultForm)} + /> + )} + + + {myTeams.map((team) => ( + + + + {team.teamName} + + {userInfo?.team?.teamId === team.teamId ? ( + + ) : ( + + )} + + ))} + + + {/* team card */} + + + + {userInfo.team.teamName} + + {userInfo.team.role === TeamMemberRoleEnum.owner && ( + { + if (!userInfo?.team) return; + setEditTeamData({ + id: userInfo.team.teamId, + name: userInfo.team.teamName, + avatar: userInfo.team.avatar + }); + }} + /> + )} + + + + {t('user.team.Member')} + + {members.length} + + {userInfo.team.role === TeamMemberRoleEnum.owner && ( + + )} + + {userInfo.team.role !== TeamMemberRoleEnum.owner && ( + + )} + + + + + + + + + + + + + + {members.map((item) => ( + + + + + + + ))} + +
{t('common.Username')}{t('user.team.Role')}{t('common.Status')}
+ + + {item.memberUsername} + + {t(TeamMemberRoleMap[item.role]?.label || '')} + {t(TeamMemberStatusMap[item.status]?.label || '')} + + {userInfo?.team?.role === TeamMemberRoleEnum.owner && + item.role !== TeamMemberRoleEnum.owner && ( + + + + } + menuList={[ + { + isActive: item.role === TeamMemberRoleEnum.visitor, + child: t('user.team.Invite Role Visitor Tip'), + onClick: () => { + onUpdateMember({ + teamId: item.teamId, + memberId: item.tmbId, + role: TeamMemberRoleEnum.visitor + }); + } + }, + { + isActive: item.role === TeamMemberRoleEnum.admin, + child: t('user.team.Invite Role Admin Tip'), + onClick: () => { + onUpdateMember({ + teamId: item.teamId, + memberId: item.tmbId, + role: TeamMemberRoleEnum.admin + }); + } + }, + ...(item.status === TeamMemberStatusEnum.reject + ? [ + { + child: t('user.team.Reinvite'), + onClick: () => { + onUpdateMember({ + teamId: item.teamId, + memberId: item.tmbId, + status: TeamMemberStatusEnum.waiting + }); + } + } + ] + : []), + { + child: t('user.team.Remove Member Tip'), + onClick: () => + openRemoveMember( + () => + onRemoveMember({ + teamId: item.teamId, + memberId: item.tmbId + }), + undefined, + t('user.team.Remove Member Confirm Tip', { + username: item.memberUsername + }) + )() + } + ]} + /> + )} +
+
+
+
+ +
+
+ {!!editTeamData && ( + setEditTeamData(undefined)} + onSuccess={() => { + refetchTeam(); + initUserInfo(); + }} + /> + )} + {isOpenInvite && userInfo?.team?.teamId && ( + + )} + + + + ) : null; +}; + +export default React.memo(TeamManageModal); diff --git a/projects/app/src/components/support/user/team/TeamMenu/index.tsx b/projects/app/src/components/support/user/team/TeamMenu/index.tsx new file mode 100644 index 000000000..e49934bcc --- /dev/null +++ b/projects/app/src/components/support/user/team/TeamMenu/index.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { Box, Button, Flex, Image, useDisclosure, useTheme } from '@chakra-ui/react'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import { useTranslation } from 'react-i18next'; +import MyTooltip from '@/components/MyTooltip'; +import dynamic from 'next/dynamic'; +import { feConfigs } from '@/web/common/system/staticData'; +import { useToast } from '@/web/common/hooks/useToast'; + +const TeamManageModal = dynamic(() => import('../TeamManageModal')); + +const TeamMenu = () => { + const theme = useTheme(); + const { t } = useTranslation(); + const { userInfo } = useUserStore(); + const { toast } = useToast(); + + const { isOpen, onOpen, onClose } = useDisclosure(); + + return ( + + ); +}; + +export default TeamMenu; diff --git a/projects/app/src/components/support/user/team/UpdateInviteModal/index.tsx b/projects/app/src/components/support/user/team/UpdateInviteModal/index.tsx new file mode 100644 index 000000000..f90496081 --- /dev/null +++ b/projects/app/src/components/support/user/team/UpdateInviteModal/index.tsx @@ -0,0 +1,133 @@ +import React, { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import MyModal from '@/components/MyModal'; +import { + Button, + ModalFooter, + useDisclosure, + ModalBody, + Flex, + Box, + useTheme +} from '@chakra-ui/react'; +import { useQuery } from '@tanstack/react-query'; +import { getTeamList, updateInviteResult } from '@/web/support/user/team/api'; +import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant'; +import Avatar from '@/components/Avatar'; +import { useRequest } from '@/web/common/hooks/useRequest'; +import { useToast } from '@/web/common/hooks/useToast'; +import { useConfirm } from '@/web/common/hooks/useConfirm'; +import { feConfigs } from '@/web/common/system/staticData'; + +const UpdateInviteModal = () => { + const { t } = useTranslation(); + const theme = useTheme(); + const { toast } = useToast(); + const { ConfirmModal, openConfirm } = useConfirm({}); + + const { data: inviteList = [], refetch } = useQuery(['getInviteList'], () => + feConfigs.isPlus ? getTeamList(TeamMemberStatusEnum.waiting) : [] + ); + + const { mutate: onAccept, isLoading: isLoadingAccept } = useRequest({ + mutationFn: updateInviteResult, + onSuccess() { + toast({ + status: 'success', + title: t('user.team.invite.Accepted') + }); + refetch(); + } + }); + const { mutate: onReject, isLoading: isLoadingReject } = useRequest({ + mutationFn: updateInviteResult, + onSuccess() { + toast({ + status: 'success', + title: t('user.team.invite.Reject') + }); + refetch(); + } + }); + + return ( + 0} + title={ + <> + {t('user.team.Processing invitations')} + + {t('user.team.Processing invitations Tips', { amount: inviteList.length })} + + + } + maxW={['90vw', '500px']} + > + + {inviteList.map((item) => ( + + + {item.teamName} + + + + + ))} + + + {t('user.team.invite.Deal Width Footer Tip')} + + + + + ); +}; + +export default React.memo(UpdateInviteModal); diff --git a/projects/app/src/constants/app.ts b/projects/app/src/constants/app.ts index 523157014..49229d257 100644 --- a/projects/app/src/constants/app.ts +++ b/projects/app/src/constants/app.ts @@ -1,4 +1,32 @@ -/* app */ +import { AppDetailType } from '@fastgpt/global/core/app/type.d'; +import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; + +export const defaultApp: AppDetailType = { + _id: '', + userId: 'userId', + name: '模型加载中', + type: 'basic', + avatar: '/icon/logo.svg', + intro: '', + updateTime: Date.now(), + modules: [], + teamId: '', + tmbId: '', + permission: 'private', + isOwner: false, + canWrite: false +}; + +export const defaultOutLinkForm: OutLinkEditType = { + name: '', + responseDetail: false, + limit: { + QPM: 100, + credit: -1 + } +}; + +/* module special */ export enum SystemInputEnum { 'welcomeText' = 'welcomeText', 'variables' = 'variables', @@ -6,6 +34,7 @@ export enum SystemInputEnum { 'history' = 'history', 'userChatInput' = 'userChatInput', 'questionGuide' = 'questionGuide', + 'tts' = 'tts', isResponseAnswerText = 'isResponseAnswerText' } export enum SystemOutputEnum { @@ -17,7 +46,8 @@ export enum VariableInputEnum { select = 'select' } -export enum AppTypeEnum { - basic = 'basic', - advanced = 'advanced' +export enum TTSTypeEnum { + none = 'none', + web = 'web', + model = 'model' } diff --git a/projects/app/src/constants/dataset.ts b/projects/app/src/constants/dataset.ts index 7ac1e48a7..2497c5b9e 100644 --- a/projects/app/src/constants/dataset.ts +++ b/projects/app/src/constants/dataset.ts @@ -1,11 +1,19 @@ import type { DatasetItemType } from '@/types/core/dataset'; -export const defaultKbDetail: DatasetItemType = { +export const defaultDatasetDetail: DatasetItemType = { _id: '', + parentId: '', userId: '', + teamId: '', + tmbId: '', + updateTime: new Date(), + type: 'dataset', avatar: '/icon/logo.svg', name: '', tags: '', + permission: 'private', + isOwner: false, + canWrite: false, vectorModel: { model: 'text-embedding-ada-002', name: 'Embedding-2', diff --git a/projects/app/src/constants/flow/ModuleTemplate.ts b/projects/app/src/constants/flow/ModuleTemplate.ts index 81416612b..be9150710 100644 --- a/projects/app/src/constants/flow/ModuleTemplate.ts +++ b/projects/app/src/constants/flow/ModuleTemplate.ts @@ -1,5 +1,6 @@ -import { AppTypeEnum, SystemInputEnum } from '../app'; -import { TaskResponseKeyEnum } from '../chat'; +import { SystemInputEnum } from '../app'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; +import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; import { FlowNodeTypeEnum, FlowNodeInputTypeEnum, @@ -27,7 +28,6 @@ export const welcomeTextTip = '每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题'; export const variableTip = '可以在对话开始前,要求用户填写一些内容作为本轮对话的特定变量。该模块位于开场引导之后。\n变量可以通过 {{变量key}} 的形式注入到其他模块 string 类型的输入中,例如:提示词、限定词等'; -export const questionGuideTip = `对话结束后,会为生成 3 个引导性问题。`; export const VariableModule: FlowModuleTemplateType = { id: FlowNodeTypeEnum.variable, @@ -69,6 +69,11 @@ export const UserGuideModule: FlowModuleTemplateType = { key: SystemInputEnum.questionGuide, type: FlowNodeInputTypeEnum.switch, label: '问题引导' + }, + { + key: SystemInputEnum.tts, + type: FlowNodeInputTypeEnum.hidden, + label: '语音播报' } ], outputs: [] @@ -162,15 +167,15 @@ export const ChatModule: FlowModuleTemplateType = { key: 'maxToken', type: FlowNodeInputTypeEnum.hidden, label: '回复上限', - value: chatModelList?.[0] ? chatModelList[0].maxToken / 2 : 2000, + value: chatModelList?.[0] ? chatModelList[0].maxResponse / 2 : 2000, min: 100, - max: chatModelList?.[0]?.maxToken || 4000, + max: chatModelList?.[0]?.maxResponse || 4000, step: 50, markList: [ { label: '100', value: 100 }, { - label: `${chatModelList?.[0]?.maxToken || 4000}`, - value: chatModelList?.[0]?.maxToken || 4000 + label: `${chatModelList?.[0]?.maxResponse || 4000}`, + value: chatModelList?.[0]?.maxResponse || 4000 } ] }, diff --git a/projects/app/src/constants/plugin.ts b/projects/app/src/constants/plugin.ts deleted file mode 100644 index 92db0e358..000000000 --- a/projects/app/src/constants/plugin.ts +++ /dev/null @@ -1 +0,0 @@ -export const PgDatasetTableName = 'modeldata'; diff --git a/projects/app/src/constants/user.ts b/projects/app/src/constants/user.ts index 0298d088a..05f6121d1 100644 --- a/projects/app/src/constants/user.ts +++ b/projects/app/src/constants/user.ts @@ -1,26 +1,9 @@ -export enum OAuthEnum { - github = 'github', - google = 'google' -} -export enum BillSourceEnum { - fastgpt = 'fastgpt', - api = 'api', - shareLink = 'shareLink', - training = 'training' -} export enum PageTypeEnum { login = 'login', register = 'register', forgetPassword = 'forgetPassword' } -export const BillSourceMap: Record<`${BillSourceEnum}`, string> = { - [BillSourceEnum.fastgpt]: '在线使用', - [BillSourceEnum.api]: 'Api', - [BillSourceEnum.shareLink]: '免登录链接', - [BillSourceEnum.training]: '数据训练' -}; - export enum PromotionEnum { register = 'register', pay = 'pay' diff --git a/projects/app/src/global/common/api/systemRes.d.ts b/projects/app/src/global/common/api/systemRes.d.ts index adbbd347e..0db364615 100644 --- a/projects/app/src/global/common/api/systemRes.d.ts +++ b/projects/app/src/global/common/api/systemRes.d.ts @@ -3,7 +3,8 @@ import type { FunctionModelItemType, LLMModelItemType, VectorModelItemType -} from '@/types/model'; +} from '@fastgpt/global/core/ai/model.d'; + import type { FeConfigsType } from '@fastgpt/global/common/system/types/index.d'; export type InitDateResponse = { diff --git a/projects/app/src/global/common/tiktoken/index.ts b/projects/app/src/global/common/tiktoken/index.ts index 29fa3a0c1..61e521cbb 100644 --- a/projects/app/src/global/common/tiktoken/index.ts +++ b/projects/app/src/global/common/tiktoken/index.ts @@ -1,5 +1,5 @@ /* Only the token of gpt-3.5-turbo is used */ -import { ChatItemType } from '@/types/chat'; +import type { ChatItemType } from '@fastgpt/global/core/chat/type'; import { Tiktoken } from 'js-tiktoken/lite'; import { adaptChat2GptMessages } from '@/utils/common/adapt/message'; import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constant'; diff --git a/projects/app/src/global/core/ai/api.d.ts b/projects/app/src/global/core/ai/api.d.ts new file mode 100644 index 000000000..f474a7f32 --- /dev/null +++ b/projects/app/src/global/core/ai/api.d.ts @@ -0,0 +1,6 @@ +import { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d'; + +export type CreateQuestionGuideParams = { + messages: ChatMessageItemType[]; + shareId?: string; +}; diff --git a/projects/app/src/global/core/api/aiReq.d.ts b/projects/app/src/global/core/api/aiReq.d.ts deleted file mode 100644 index 8d856b055..000000000 --- a/projects/app/src/global/core/api/aiReq.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ChatCompletionRequestMessage } from '@fastgpt/global/core/ai/type.d'; - -export type CreateQuestionGuideParams = { - messages: ChatCompletionRequestMessage[]; - shareId?: string; -}; diff --git a/projects/app/src/global/core/api/appRes.d.ts b/projects/app/src/global/core/api/appRes.d.ts deleted file mode 100644 index 0ac52a2fd..000000000 --- a/projects/app/src/global/core/api/appRes.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { AppListItemType } from '@/types/app'; - -export type AppListResponse = { - myApps: AppListItemType[]; - myCollectionApps: AppListItemType[]; -}; diff --git a/projects/app/src/global/core/api/chatReq.d.ts b/projects/app/src/global/core/api/chatReq.d.ts deleted file mode 100644 index 29b56f95d..000000000 --- a/projects/app/src/global/core/api/chatReq.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { MarkDataType } from '../dataset/type'; - -export type AdminUpdateFeedbackParams = MarkDataType & { - chatItemId: string; -}; diff --git a/projects/app/src/global/core/api/chatRes.d.ts b/projects/app/src/global/core/api/chatRes.d.ts deleted file mode 100644 index dcff87bdf..000000000 --- a/projects/app/src/global/core/api/chatRes.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { AppSchema } from '@/types/mongoSchema'; -import type { ChatItemType } from '@/types/chat'; -import { VariableItemType } from '@/types/app'; -import type { ModuleItemType } from '@fastgpt/global/core/module/type'; - -export type InitChatResponse = { - chatId: string; - appId: string; - app: { - userGuideModule?: ModuleItemType; - chatModels?: string[]; - name: string; - avatar: string; - intro: string; - canUse?: boolean; - }; - title: string; - variables: Record; - history: ChatItemType[]; -}; diff --git a/projects/app/src/global/core/api/datasetReq.d.ts b/projects/app/src/global/core/api/datasetReq.d.ts index b34951a2d..ef9427449 100644 --- a/projects/app/src/global/core/api/datasetReq.d.ts +++ b/projects/app/src/global/core/api/datasetReq.d.ts @@ -4,6 +4,7 @@ import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constant'; import type { SearchTestItemType } from '@/types/core/dataset'; import { DatasetChunkItemType, UploadChunkItemType } from '@fastgpt/global/core/dataset/type'; import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type'; +import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; /* ===== dataset ===== */ export type DatasetUpdateParams = { @@ -12,11 +13,12 @@ export type DatasetUpdateParams = { tags?: string; name?: string; avatar?: string; + permission?: `${PermissionTypeEnum}`; }; export type CreateDatasetParams = { parentId?: string; name: string; - tags: string[]; + tags: string; avatar: string; vectorModel?: string; type: `${DatasetTypeEnum}`; @@ -25,6 +27,7 @@ export type CreateDatasetParams = { export type SearchTestProps = { datasetId: string; text: string; + limit?: number; }; /* ======= collections =========== */ @@ -53,7 +56,6 @@ export type UpdateDatasetCollectionParams = { /* ==== data ===== */ export type SetOneDatasetDataProps = { id?: string; - datasetId: string; collectionId: string; q?: string; // embedding content a?: string; // bonus content diff --git a/projects/app/src/global/core/api/datasetRes.d.ts b/projects/app/src/global/core/api/datasetRes.d.ts index d982fcad5..ceac0965d 100644 --- a/projects/app/src/global/core/api/datasetRes.d.ts +++ b/projects/app/src/global/core/api/datasetRes.d.ts @@ -1,7 +1,6 @@ import type { RequestPaging } from '@/types'; import { TrainingModeEnum } from '@/constants/plugin'; import type { SearchTestItemType } from '@/types/core/dataset'; -import { DatasetDataItemType } from '@/types/core/dataset/data'; import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type'; /* ===== dataset ===== */ diff --git a/projects/app/src/global/core/app/modules/utils.ts b/projects/app/src/global/core/app/modules/utils.ts index fec893634..4fef8cfa5 100644 --- a/projects/app/src/global/core/app/modules/utils.ts +++ b/projects/app/src/global/core/app/modules/utils.ts @@ -1,6 +1,6 @@ import { SystemInputEnum } from '@/constants/app'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant'; -import { VariableItemType } from '@/types/app'; +import { AppTTSConfigType, VariableItemType } from '@/types/app'; import type { ModuleItemType } from '@fastgpt/global/core/module/type'; export const getGuideModule = (modules: ModuleItemType[]) => @@ -17,9 +17,14 @@ export const splitGuideModule = (guideModules?: ModuleItemType) => { guideModules?.inputs?.find((item) => item.key === SystemInputEnum.questionGuide)?.value || false; + const ttsConfig: AppTTSConfigType = guideModules?.inputs?.find( + (item) => item.key === SystemInputEnum.tts + )?.value || { type: 'web' }; + return { welcomeText, variableModules, - questionGuide + questionGuide, + ttsConfig }; }; diff --git a/projects/app/src/global/core/chat/api.d.ts b/projects/app/src/global/core/chat/api.d.ts new file mode 100644 index 000000000..54d286356 --- /dev/null +++ b/projects/app/src/global/core/chat/api.d.ts @@ -0,0 +1,7 @@ +import type { AppTTSConfigType } from '@/types/app'; + +export type GetChatSpeechProps = { + chatItemId?: string; + ttsConfig: AppTTSConfigType; + input: string; +}; diff --git a/projects/app/src/global/core/dataset/response.d.ts b/projects/app/src/global/core/dataset/response.d.ts index f75ec6352..0a216ec74 100644 --- a/projects/app/src/global/core/dataset/response.d.ts +++ b/projects/app/src/global/core/dataset/response.d.ts @@ -7,12 +7,14 @@ import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type.d export type DatasetCollectionsListItemType = { _id: string; parentId?: string; + tmbId: string; name: string; type: DatasetCollectionSchemaType['type']; updateTime: Date; dataAmount?: number; trainingAmount: number; metadata: DatasetCollectionSchemaType['metadata']; + canWrite: boolean; }; /* ================= data ===================== */ diff --git a/projects/app/src/global/core/dataset/type.d.ts b/projects/app/src/global/core/dataset/type.d.ts deleted file mode 100644 index 94f848b53..000000000 --- a/projects/app/src/global/core/dataset/type.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type MarkDataType = { - dataId: string; - datasetId: string; - collectionId: string; - q: string; - a?: string; -}; diff --git a/projects/app/src/global/support/api/outLinkRes.d.ts b/projects/app/src/global/support/api/outLinkRes.d.ts deleted file mode 100644 index 9dace2ccd..000000000 --- a/projects/app/src/global/support/api/outLinkRes.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { InitChatResponse } from '@/global/core/api/chatRes.d'; - -export type InitShareChatResponse = { - userAvatar: string; - app: InitChatResponse['app']; -}; diff --git a/projects/app/src/global/support/api/userRes.d.ts b/projects/app/src/global/support/api/userRes.d.ts index b329a7025..28ecce5cb 100644 --- a/projects/app/src/global/support/api/userRes.d.ts +++ b/projects/app/src/global/support/api/userRes.d.ts @@ -1,4 +1,4 @@ -import type { UserType } from '@/types/user'; +import type { UserTypee } from '@fastgpt/global/support/user/type.d'; import type { PromotionRecordSchema } from '@fastgpt/global/support/activity/type.d'; export interface ResLogin { user: UserType; diff --git a/projects/app/src/global/support/api/openapiReq.d.ts b/projects/app/src/global/support/openapi/api.d.ts similarity index 100% rename from projects/app/src/global/support/api/openapiReq.d.ts rename to projects/app/src/global/support/openapi/api.d.ts diff --git a/projects/app/src/pages/account/components/BillDetail.tsx b/projects/app/src/pages/account/components/BillDetail.tsx index 2c7e2f20f..140060a52 100644 --- a/projects/app/src/pages/account/components/BillDetail.tsx +++ b/projects/app/src/pages/account/components/BillDetail.tsx @@ -11,14 +11,14 @@ import { Td, TableContainer } from '@chakra-ui/react'; -import { UserBillType } from '@/types/user'; +import { BillItemType } from '@fastgpt/global/support/wallet/bill/type.d'; import dayjs from 'dayjs'; -import { BillSourceMap } from '@/constants/user'; -import { formatPrice } from '@fastgpt/global/common/bill/tools'; +import { BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants'; +import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; import MyModal from '@/components/MyModal'; import { useTranslation } from 'react-i18next'; -const BillDetail = ({ bill, onClose }: { bill: UserBillType; onClose: () => void }) => { +const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void }) => { const { t } = useTranslation(); const filterBillList = useMemo( () => bill.list.filter((item) => item && item.moduleName), @@ -28,6 +28,10 @@ const BillDetail = ({ bill, onClose }: { bill: UserBillType; onClose: () => void return ( + + 用户: + {bill.username} + 订单号: {bill.id} @@ -65,7 +69,7 @@ const BillDetail = ({ bill, onClose }: { bill: UserBillType; onClose: () => void {filterBillList.map((item, i) => ( - {item.moduleName} + {t(item.moduleName)} {item.model} {item.tokenLen} {formatPrice(item.amount)} diff --git a/projects/app/src/pages/account/components/BillTable.tsx b/projects/app/src/pages/account/components/BillTable.tsx index b9b5dfb19..83cf34bb0 100644 --- a/projects/app/src/pages/account/components/BillTable.tsx +++ b/projects/app/src/pages/account/components/BillTable.tsx @@ -11,9 +11,9 @@ import { Box, Button } from '@chakra-ui/react'; -import { BillSourceMap } from '@/constants/user'; -import { getUserBills } from '@/web/common/bill/api'; -import type { UserBillType } from '@/types/user'; +import { BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants'; +import { getUserBills } from '@/web/support/wallet/bill/api'; +import type { BillItemType } from '@fastgpt/global/support/wallet/bill/type'; import { usePagination } from '@/web/common/hooks/usePagination'; import { useLoading } from '@/web/common/hooks/useLoading'; import dayjs from 'dayjs'; @@ -33,14 +33,14 @@ const BillTable = () => { to: new Date() }); const { isPc } = useSystemStore(); - const [billDetail, setBillDetail] = useState(); + const [billDetail, setBillDetail] = useState(); const { data: bills, isLoading, Pagination, getData - } = usePagination({ + } = usePagination({ api: getUserBills, pageSize: isPc ? 20 : 10, params: { @@ -55,6 +55,7 @@ const BillTable = () => { + @@ -65,6 +66,7 @@ const BillTable = () => { {bills.map((item) => ( + diff --git a/projects/app/src/pages/account/components/Info.tsx b/projects/app/src/pages/account/components/Info.tsx index 2ec3ac225..b7f5acbd3 100644 --- a/projects/app/src/pages/account/components/Info.tsx +++ b/projects/app/src/pages/account/components/Info.tsx @@ -1,26 +1,14 @@ import React, { useCallback, useRef, useState } from 'react'; -import { - Box, - Flex, - Button, - useDisclosure, - useTheme, - Divider, - Select, - Menu, - MenuButton, - MenuList, - MenuItem -} from '@chakra-ui/react'; +import { Box, Flex, Button, useDisclosure, useTheme, Divider, Select } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; import { UserUpdateParams } from '@/types/user'; import { useToast } from '@/web/common/hooks/useToast'; import { useUserStore } from '@/web/support/user/useUserStore'; -import { UserType } from '@/types/user'; +import type { UserType } from '@fastgpt/global/support/user/type.d'; import { useQuery } from '@tanstack/react-query'; import dynamic from 'next/dynamic'; import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; -import { compressImg } from '@/web/common/file/utils'; +import { compressImgAndUpload } from '@/web/common/file/controller'; import { feConfigs, systemVersion } from '@/web/common/system/staticData'; import { useTranslation } from 'next-i18next'; import { timezoneList } from '@fastgpt/global/common/time/timezone'; @@ -30,9 +18,10 @@ import MyIcon from '@/components/Icon'; import MyTooltip from '@/components/MyTooltip'; import { getLangStore, LangEnum, langMap, setLangStore } from '@/web/common/utils/i18n'; import { useRouter } from 'next/router'; -import MyMenu from '@/components/MyMenu'; import MySelect from '@/components/Select'; +import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; +const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu')); const PayModal = dynamic(() => import('./PayModal'), { loading: () => , ssr: false @@ -97,7 +86,7 @@ const UserInfo = () => { const file = e[0]; if (!file || !userInfo) return; try { - const src = await compressImg({ + const src = await compressImgAndUpload({ file, maxW: 100, maxH: 100 @@ -168,6 +157,12 @@ const UserInfo = () => { {t('user.Account')}:  {userInfo?.username} + + {t('user.Team')}:  + + + + {t('user.Language')}:  @@ -212,11 +207,13 @@ const UserInfo = () => { - {t('user.Balance')}:  - - {userInfo?.balance.toFixed(3)} 元 + + {t('user.team.Balance')}:  - {feConfigs?.show_pay && ( + + {formatPrice(userInfo?.team?.balance).toFixed(3)} 元 + + {feConfigs?.show_pay && userInfo?.team?.canWrite && ( diff --git a/projects/app/src/pages/account/components/InformTable.tsx b/projects/app/src/pages/account/components/InformTable.tsx index e04b7bce8..44b0b45fc 100644 --- a/projects/app/src/pages/account/components/InformTable.tsx +++ b/projects/app/src/pages/account/components/InformTable.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Box, Flex, useTheme } from '@chakra-ui/react'; -import { getInforms, readInform } from '@/web/support/user/api'; +import { getInforms, readInform } from '@/web/support/user/inform/api'; import { usePagination } from '@/web/common/hooks/usePagination'; import { useLoading } from '@/web/common/hooks/useLoading'; -import type { UserInformSchema } from '@fastgpt/global/support/user/type'; +import type { UserInformSchema } from '@fastgpt/global/support/user/inform/type'; import { formatTimeToChatTime } from '@/utils/tools'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import MyIcon from '@/components/Icon'; @@ -67,15 +67,16 @@ const BillTable = () => { )} ))} + {!isLoading && informs.length === 0 && ( + + + + 暂无通知~ + + + )} - {!isLoading && informs.length === 0 && ( - - - - 暂无通知~ - - - )} + {total > pageSize && ( diff --git a/projects/app/src/pages/account/components/OpenAIAccountModal.tsx b/projects/app/src/pages/account/components/OpenAIAccountModal.tsx index 8b96d321b..e7df7a487 100644 --- a/projects/app/src/pages/account/components/OpenAIAccountModal.tsx +++ b/projects/app/src/pages/account/components/OpenAIAccountModal.tsx @@ -4,7 +4,7 @@ import MyModal from '@/components/MyModal'; import { useTranslation } from 'react-i18next'; import { useForm } from 'react-hook-form'; import { useRequest } from '@/web/common/hooks/useRequest'; -import { UserType } from '@/types/user'; +import type { UserType } from '@fastgpt/global/support/user/type.d'; const OpenAIAccountModal = ({ defaultData, diff --git a/projects/app/src/pages/account/components/PayModal.tsx b/projects/app/src/pages/account/components/PayModal.tsx index b0d5478c0..3023e6e41 100644 --- a/projects/app/src/pages/account/components/PayModal.tsx +++ b/projects/app/src/pages/account/components/PayModal.tsx @@ -1,6 +1,6 @@ import React, { useState, useCallback } from 'react'; import { ModalFooter, ModalBody, Button, Input, Box, Grid } from '@chakra-ui/react'; -import { getPayCode, checkPayResult } from '@/web/common/bill/api'; +import { getPayCode, checkPayResult } from '@/web/support/wallet/pay/api'; import { useToast } from '@/web/common/hooks/useToast'; import { useQuery } from '@tanstack/react-query'; import { useRouter } from 'next/router'; diff --git a/projects/app/src/pages/account/components/PayRecordTable.tsx b/projects/app/src/pages/account/components/PayRecordTable.tsx index b86bfdd15..ef9b08985 100644 --- a/projects/app/src/pages/account/components/PayRecordTable.tsx +++ b/projects/app/src/pages/account/components/PayRecordTable.tsx @@ -11,11 +11,11 @@ import { Flex, Box } from '@chakra-ui/react'; -import { getPayOrders, checkPayResult } from '@/web/common/bill/api'; -import type { PaySchema } from '@fastgpt/global/support/wallet/type.d'; +import { getPayOrders, checkPayResult } from '@/web/support/wallet/pay/api'; +import type { PaySchema } from '@fastgpt/global/support/wallet/pay/type.d'; import dayjs from 'dayjs'; import { useQuery } from '@tanstack/react-query'; -import { formatPrice } from '@fastgpt/global/common/bill/tools'; +import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; import { useToast } from '@/web/common/hooks/useToast'; import { useLoading } from '@/web/common/hooks/useLoading'; import MyIcon from '@/components/Icon'; diff --git a/projects/app/src/pages/account/components/Promotion.tsx b/projects/app/src/pages/account/components/Promotion.tsx index f1daec2ae..afece989d 100644 --- a/projects/app/src/pages/account/components/Promotion.tsx +++ b/projects/app/src/pages/account/components/Promotion.tsx @@ -16,7 +16,7 @@ import { } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import { useQuery } from '@tanstack/react-query'; -import { getPromotionInitData, getPromotionRecords } from '@/web/support/user/api'; +import { getPromotionInitData, getPromotionRecords } from '@/web/support/activity/promotion/api'; import { useUserStore } from '@/web/support/user/useUserStore'; import { useLoading } from '@/web/common/hooks/useLoading'; @@ -42,7 +42,8 @@ const Promotion = () => { pageSize, Pagination } = usePagination({ - api: getPromotionRecords + api: getPromotionRecords, + pageSize: 20 }); const { data: { invitedAmount = 0, earningsAmount = 0 } = {} } = useQuery( diff --git a/projects/app/src/pages/account/components/UpdatePswModal.tsx b/projects/app/src/pages/account/components/UpdatePswModal.tsx index 1a09becd7..023ed2310 100644 --- a/projects/app/src/pages/account/components/UpdatePswModal.tsx +++ b/projects/app/src/pages/account/components/UpdatePswModal.tsx @@ -32,7 +32,7 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => { onSuccess() { onClose(); }, - successToast: t('user.Update password succseful'), + successToast: t('user.Update password successful'), errorToast: t('user.Update password failed') }); diff --git a/projects/app/src/pages/account/index.tsx b/projects/app/src/pages/account/index.tsx index 31d026d83..d293b6be1 100644 --- a/projects/app/src/pages/account/index.tsx +++ b/projects/app/src/pages/account/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useRef } from 'react'; +import React, { useCallback, useMemo, useRef } from 'react'; import { Box, Flex, useTheme } from '@chakra-ui/react'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useRouter } from 'next/router'; @@ -33,52 +33,69 @@ enum TabEnum { const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => { const { t } = useTranslation(); - const tabList = [ - { - icon: 'meLight', - label: t('user.Personal Information'), - id: TabEnum.info - }, + const { userInfo, setUserInfo } = useUserStore(); - { - icon: 'billRecordLight', - label: t('user.Usage Record'), - id: TabEnum.bill - }, - ...(feConfigs?.show_promotion - ? [ - { - icon: 'promotionLight', - label: t('user.Promotion Record'), - id: TabEnum.promotion - } - ] - : []), - ...(feConfigs?.show_pay - ? [ - { - icon: 'payRecordLight', - label: t('user.Recharge Record'), - id: TabEnum.pay - } - ] - : []), - { - icon: 'apikey', - label: t('user.apikey.key'), - id: TabEnum.apikey - }, - { - icon: 'informLight', - label: t('user.Notice'), - id: TabEnum.inform - }, - { - icon: 'loginoutLight', - label: t('user.Sign Out'), - id: TabEnum.loginout - } - ]; + const tabList = useMemo( + () => [ + { + icon: 'meLight', + label: t('user.Personal Information'), + id: TabEnum.info + }, + ...(feConfigs?.isPlus + ? [ + { + icon: 'billRecordLight', + label: t('user.Usage Record'), + id: TabEnum.bill + } + ] + : []), + ...(feConfigs?.show_promotion + ? [ + { + icon: 'promotionLight', + label: t('user.Promotion Record'), + id: TabEnum.promotion + } + ] + : []), + ...(feConfigs?.show_pay && userInfo?.team.canWrite + ? [ + { + icon: 'payRecordLight', + label: t('user.Recharge Record'), + id: TabEnum.pay + } + ] + : []), + ...(userInfo?.team.canWrite + ? [ + { + icon: 'apikey', + label: t('user.apikey.key'), + id: TabEnum.apikey + } + ] + : []), + ...(feConfigs.isPlus + ? [ + { + icon: 'informLight', + label: t('user.Notice'), + id: TabEnum.inform + } + ] + : []), + + { + icon: 'loginoutLight', + label: t('user.Sign Out'), + id: TabEnum.loginout + } + ], + [t, userInfo?.team.canWrite] + ); const { openConfirm, ConfirmModal } = useConfirm({ content: '确认退出登录?' @@ -87,7 +104,6 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => { const router = useRouter(); const theme = useTheme(); const { isPc } = useSystemStore(); - const { setUserInfo } = useUserStore(); const setCurrentTab = useCallback( (tab: string) => { diff --git a/projects/app/src/pages/api/admin/initv44.ts b/projects/app/src/pages/api/admin/initv44.ts deleted file mode 100644 index 67c49eee8..000000000 --- a/projects/app/src/pages/api/admin/initv44.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { connectToDatabase } from '@/service/mongo'; -import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; -import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant'; -import { PgClient } from '@/service/pg'; -import { PgDatasetTableName } from '@/constants/plugin'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - await authUser({ req, authRoot: true }); - - await MongoDataset.updateMany( - { - type: { $exists: false } - }, - { - $set: { - type: DatasetTypeEnum.dataset, - parentId: null - } - } - ); - - const response = await PgClient.update(PgDatasetTableName, { - where: [['file_id', 'undefined']], - values: [{ key: 'file_id', value: '' }] - }); - - jsonRes(res, { - data: response.rowCount - }); - } catch (error) { - jsonRes(res, { - code: 500, - error - }); - } -} diff --git a/projects/app/src/pages/api/admin/initv441.ts b/projects/app/src/pages/api/admin/initv441.ts deleted file mode 100644 index 79bd92440..000000000 --- a/projects/app/src/pages/api/admin/initv441.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { connectToDatabase } from '@/service/mongo'; -import mongoose from '@fastgpt/service/common/mongo'; -import { PgClient } from '@/service/pg'; -import { PgDatasetTableName } from '@/constants/plugin'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - await authUser({ req, authRoot: true }); - - const data = await mongoose.connection.db - .collection('dataset.files') - .updateMany({}, { $set: { 'metadata.datasetUsed': true } }); - - // update pg data - const pg = await PgClient.query(`UPDATE ${PgDatasetTableName} - SET file_id = '' - WHERE (file_id = 'undefined' OR LENGTH(file_id) < 20) AND file_id != '';`); - - jsonRes(res, { - data: { - data, - pg - } - }); - } catch (error) { - jsonRes(res, { - code: 500, - error - }); - } -} diff --git a/projects/app/src/pages/api/admin/initv442.ts b/projects/app/src/pages/api/admin/initv442.ts deleted file mode 100644 index b719b16a0..000000000 --- a/projects/app/src/pages/api/admin/initv442.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { connectToDatabase, Bill } from '@/service/mongo'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - await authUser({ req, authRoot: true }); - - try { - await Bill.collection.dropIndex('time_1'); - } catch (error) {} - try { - await Bill.collection.createIndex({ time: 1 }, { expireAfterSeconds: 90 * 24 * 60 * 60 }); - } catch (error) {} - - jsonRes(res, { - data: {} - }); - } catch (error) { - jsonRes(res, { - code: 500, - error - }); - } -} diff --git a/projects/app/src/pages/api/admin/initv445.ts b/projects/app/src/pages/api/admin/initv445.ts deleted file mode 100644 index 5ec69bf59..000000000 --- a/projects/app/src/pages/api/admin/initv445.ts +++ /dev/null @@ -1,104 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { connectToDatabase, App } from '@/service/mongo'; -import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant'; -import { SystemInputEnum } from '@/constants/app'; - -const limit = 300; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - await authUser({ req, authRoot: true }); - - const totalApps = await App.countDocuments(); - - // init app - await App.updateMany({}, { $set: { inited: false } }); - - for (let i = 0; i < totalApps; i += limit) { - await initVariable(); - console.log(i + limit); - } - - jsonRes(res, { - data: { - total: totalApps - } - }); - } catch (error) { - jsonRes(res, { - code: 500, - error - }); - } -} - -async function initVariable(): Promise { - try { - const apps = await App.find({ inited: false }).limit(limit); - await Promise.all( - apps.map(async (app) => { - const jsonAPP = app.toObject(); - // @ts-ignore - app.inited = true; - const modules = jsonAPP.modules; - - // 找到 variable - const variable = modules.find((item) => item.flowType === FlowNodeTypeEnum.variable); - if (!variable) return await app.save(); - - // 找到 guide 模块 - const userGuideModule = modules.find( - (item) => item.flowType === FlowNodeTypeEnum.userGuide - ); - if (userGuideModule) { - userGuideModule.inputs = [ - userGuideModule.inputs[0], - { - key: SystemInputEnum.variables, - type: FlowNodeInputTypeEnum.systemInput, - label: '对话框变量', - value: variable.inputs[0]?.value - } - ]; - } else { - modules.unshift({ - moduleId: 'userGuide', - flowType: FlowNodeTypeEnum.userGuide, - name: '用户引导', - position: { - x: 447.98520778293346, - y: 721.4016845336229 - }, - inputs: [ - { - key: SystemInputEnum.welcomeText, - type: FlowNodeInputTypeEnum.input, - label: '开场白' - }, - { - key: SystemInputEnum.variables, - type: FlowNodeInputTypeEnum.systemInput, - label: '对话框变量', - value: variable.inputs[0]?.value - } - ], - outputs: [] - }); - } - - jsonAPP.modules = jsonAPP.modules.filter( - (item) => item.flowType !== FlowNodeTypeEnum.variable - ); - - app.modules = JSON.parse(JSON.stringify(jsonAPP.modules)); - - await app.save(); - }) - ); - } catch (error) { - return initVariable(); - } -} diff --git a/projects/app/src/pages/api/admin/initv447.ts b/projects/app/src/pages/api/admin/initv447.ts deleted file mode 100644 index 2065c8569..000000000 --- a/projects/app/src/pages/api/admin/initv447.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { connectToDatabase } from '@/service/mongo'; -import { PgClient } from '@/service/pg'; -import { PgDatasetTableName } from '@/constants/plugin'; -import { DatasetSpecialIdEnum } from '@fastgpt/global/core/dataset/constant'; -import { Types, connectionMongo } from '@fastgpt/service/common/mongo'; -import { delay } from '@/utils/tools'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - let initFileIds: string[] = []; - try { - const { limit = 100 } = req.body; - await connectToDatabase(); - await authUser({ req, authRoot: true }); - - console.log('count rows'); - // 去重获取 fileId - const { rows } = await PgClient.query(`SELECT DISTINCT file_id - FROM ${PgDatasetTableName} WHERE file_id IS NOT NULL AND file_id != ''; - `); - console.log('count rows success', rows.length); - console.log('start filter'); - for (let i = 0; i < rows.length; i += limit) { - await init(rows.slice(i, i + limit), initFileIds); - console.log(i); - } - - for (let i = 0; i < initFileIds.length; i++) { - await PgClient.query(`UPDATE ${PgDatasetTableName} - SET file_id = '${DatasetSpecialIdEnum.manual}' - WHERE file_id = '${initFileIds[i]}'`); - console.log('update: ', initFileIds[i]); - } - - const { rows: emptyIds } = await PgClient.query( - `SELECT id FROM ${PgDatasetTableName} WHERE file_id IS NULL OR file_id=''` - ); - console.log('filter success'); - console.log(emptyIds.length); - - await delay(5000); - console.log('start update'); - - async function start(start: number) { - for (let i = start; i < emptyIds.length; i += limit) { - await PgClient.query(`UPDATE ${PgDatasetTableName} - SET file_id = '${DatasetSpecialIdEnum.manual}' - WHERE id = '${emptyIds[i].id}'`); - console.log('update: ', i, emptyIds[i].id); - } - } - for (let i = 0; i < limit; i++) { - start(i); - } - - console.log('update success'); - - jsonRes(res, { - data: { - empty: emptyIds.length - } - }); - } catch (error) { - jsonRes(res, { - code: 500, - error - }); - } -} - -async function init(rows: any[], initFileIds: string[]) { - const collection = connectionMongo.connection.db.collection(`dataset.files`); - - /* 遍历所有的 fileId,去找有没有对应的文件,没有的话则改成manual */ - const updateResult = await Promise.allSettled( - rows.map(async (item) => { - // 找下是否有对应的文件 - const file = await collection.findOne({ - _id: new Types.ObjectId(item.file_id) - }); - - if (file) return ''; - // 没有文件的,改成manual - initFileIds.push(item.file_id); - - return item.file_id; - }) - ); - // @ts-ignore - console.log(updateResult.filter((item) => item?.value).length); -} diff --git a/projects/app/src/pages/api/admin/initv451.ts b/projects/app/src/pages/api/admin/initv451.ts deleted file mode 100644 index f22949290..000000000 --- a/projects/app/src/pages/api/admin/initv451.ts +++ /dev/null @@ -1,344 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { App, connectToDatabase } from '@/service/mongo'; -import { PgClient } from '@/service/pg'; -import { connectionMongo } from '@fastgpt/service/common/mongo'; -import { PgDatasetTableName } from '@/constants/plugin'; -import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant'; -import { delay } from '@/utils/tools'; -import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; -import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant'; -import { strIsLink } from '@fastgpt/global/common/string/tools'; -import { GridFSStorage } from '@/service/lib/gridfs'; -import { Types } from 'mongoose'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - const { limit = 50 } = req.body as { limit: number }; - await connectToDatabase(); - - console.log('rename'); - await rename(); - - console.log('init mongo data'); - await initMongo(limit); - - console.log('create collection'); - await createCollection(); - - console.log('update pg collectionId'); - await updatePgCollection(); - console.log('init done'); - - jsonRes(res, { - data: {} - }); - } catch (error) { - console.log(error); - - jsonRes(res, { - code: 500, - error - }); - } -} - -async function rename() { - // rename mongo kbs -> datasets - try { - const collections = await connectionMongo.connection.db - .listCollections({ name: 'kbs' }) - .toArray(); - if (collections.length > 0) { - const kbCollection = connectionMongo.connection.db.collection('kbs'); - await kbCollection.rename('datasets', { dropTarget: true }); - console.log('success rename kbs -> datasets'); - } - } catch (error) { - console.log('error: rename kbs -> datasets', error); - } - - // rename pg: kb_id -> dataset_id - try { - const { rows } = await PgClient.query(`SELECT EXISTS ( - SELECT 1 - FROM information_schema.columns - WHERE table_name = '${PgDatasetTableName}' - AND column_name = 'kb_id' - );`); - - if (rows[0].exists) { - await PgClient.query(`ALTER TABLE ${PgDatasetTableName} RENAME COLUMN kb_id TO dataset_id`); - console.log('success rename kb_id -> dataset_id'); - } - } catch (error) { - console.log('error: rename kb_id -> dataset_id', error); - } - // rename pg: file_id -> collection_id - try { - const { rows } = await PgClient.query(`SELECT EXISTS ( - SELECT 1 - FROM information_schema.columns - WHERE table_name = '${PgDatasetTableName}' - AND column_name = 'file_id' - );`); - - if (rows[0].exists) { - await PgClient.query( - `ALTER TABLE ${PgDatasetTableName} RENAME COLUMN file_id TO collection_id` - ); - console.log('success rename file_id -> collection_id'); - } - } catch (error) { - console.log('error: rename file_id -> collection_id', error); - } -} - -async function initMongo(limit: number) { - let success = 0; - - async function initApp(limit = 100): Promise { - // 遍历所有 app,更新 app modules 里的 FlowNodeTypeEnum.kbSearchNode - const apps = await App.find({ inited: false }).limit(limit); - - if (apps.length === 0) return; - - try { - await Promise.all( - apps.map(async (app) => { - const modules = app.toObject().modules; - // @ts-ignore - app.inited = true; - - modules.forEach((module) => { - // @ts-ignore - if (module.flowType === 'kbSearchNode') { - module.flowType = FlowNodeTypeEnum.datasetSearchNode; - module.inputs.forEach((input) => { - if (input.key === 'kbList') { - input.key = 'datasets'; - input.value?.forEach((item: any) => { - item.datasetId = item.kbId; - }); - } - }); - } - }); - - app.modules = JSON.parse(JSON.stringify(modules)); - await app.save(); - }) - ); - success += limit; - console.log('mongo init:', success); - return initApp(limit); - } catch (error) { - return initApp(limit); - } - } - - // init app - await App.updateMany( - {}, - { - $set: { - inited: false - } - } - ); - - const totalApp = await App.countDocuments(); - console.log(`total app: ${totalApp}`); - await delay(2000); - console.log('start init app'); - await initApp(limit); - console.log('init mongo success'); -} - -type RowType = { user_id: string; dataset_id: string; collection_id: string }; -async function createCollection() { - let success = 0; - - const { rows, rowCount } = await PgClient.query(`SELECT user_id,dataset_id,collection_id - FROM ${PgDatasetTableName} - GROUP BY user_id,collection_id, dataset_id - ORDER BY dataset_id`); - - if (rowCount === 0) { - console.log('pg done'); - return; - } - // init dataset collection - console.log(`total collection: ${rowCount}`); - - // collectionId 的类型:manual, mark, httpLink, fileId - async function initCollection(row: RowType): Promise { - try { - { - const userId = row.user_id; - const datasetId = row.dataset_id; - const collectionId = row.collection_id; - - const count = await MongoDatasetCollection.countDocuments({ - datasetId, - userId, - ['metadata.pgCollectionId']: collectionId - }); - if (count > 0) { - console.log('collection already exist'); - return; - } - - if (collectionId === 'manual') { - await MongoDatasetCollection.create({ - parentId: null, - datasetId, - userId, - name: '手动录入', - type: DatasetCollectionTypeEnum.virtual, - updateTime: new Date('2099'), - metadata: { - pgCollectionId: collectionId - } - }); - } else if (collectionId === 'mark') { - await MongoDatasetCollection.create({ - parentId: null, - datasetId, - userId, - name: '手动标注', - type: DatasetCollectionTypeEnum.virtual, - updateTime: new Date('2099'), - metadata: { - pgCollectionId: collectionId - } - }); - } else if (strIsLink(collectionId)) { - await MongoDatasetCollection.create({ - parentId: null, - datasetId, - userId, - name: collectionId, - type: DatasetCollectionTypeEnum.link, - metadata: { - rawLink: collectionId, - pgCollectionId: collectionId - } - }); - } else { - // find file - const gridFs = new GridFSStorage('dataset', userId); - const collection = gridFs.Collection(); - const file = await collection.findOne({ - _id: new Types.ObjectId(collectionId) - }); - - if (file) { - await MongoDatasetCollection.create({ - parentId: null, - datasetId, - userId, - name: file.filename, - type: DatasetCollectionTypeEnum.file, - metadata: { - fileId: file._id, - pgCollectionId: collectionId - } - }); - } else { - // no file - await MongoDatasetCollection.create({ - parentId: null, - datasetId, - userId, - name: '未知文件', - type: DatasetCollectionTypeEnum.virtual, - metadata: { - pgCollectionId: collectionId - } - }); - } - } - console.log('create collection success'); - } - } catch (error) { - console.log(error); - - await delay(2000); - return initCollection(row); - } - } - - for await (const row of rows) { - await initCollection(row); - console.log('init collection success: ', ++success); - } -} - -async function updatePgCollection(): Promise { - let success = 0; - const limit = 10; - const collections = await MongoDatasetCollection.find({ - 'metadata.pgCollectionId': { $exists: true, $ne: '' } - }).lean(); - console.log('total:', collections.length); - - async function update(i: number): Promise { - const item = collections[i]; - if (!item) return; - - try { - console.log('start', item.name, item.datasetId, item.metadata.pgCollectionId); - const time = Date.now(); - if (item.metadata.pgCollectionId) { - const { rows } = await PgClient.select(PgDatasetTableName, { - fields: ['id'], - where: [ - ['dataset_id', String(item.datasetId)], - 'AND', - ['collection_id', String(item.metadata.pgCollectionId)] - ], - limit: 999999 - }); - console.log('update date total', rows.length, 'time:', Date.now() - time); - - await PgClient.query(` - update ${PgDatasetTableName} set collection_id = '${item._id}' where dataset_id = '${String( - item.datasetId - )}' AND collection_id = '${String(item.metadata.pgCollectionId)}' - `); - - console.log('pg update time', Date.now() - time); - } - - // 更新 file id - if (item.type === 'file' && item.metadata.fileId) { - const collection = connectionMongo.connection.db.collection(`dataset.files`); - await collection.findOneAndUpdate({ _id: new Types.ObjectId(item.metadata.fileId) }, [ - { - $set: { - 'metadata.datasetId': item.datasetId, - 'metadata.collectionId': item._id - } - } - ]); - } - - await MongoDatasetCollection.findByIdAndUpdate(item._id, { - $unset: { 'metadata.pgCollectionId': '' } - }); - console.log('success', ++success); - - return update(i + limit); - } catch (error) { - console.log(error); - - await delay(5000); - return update(i); - } - } - - const arr = new Array(limit).fill(0); - - return Promise.all(arr.map((_, i) => update(i))); -} diff --git a/projects/app/src/pages/api/admin/initv46.ts b/projects/app/src/pages/api/admin/initv46.ts new file mode 100644 index 000000000..35ce38c9c --- /dev/null +++ b/projects/app/src/pages/api/admin/initv46.ts @@ -0,0 +1,335 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { MongoBill } from '@fastgpt/service/support/wallet/bill/schema'; +import { + createDefaultTeam, + getTeamInfoByTmbId +} from '@fastgpt/service/support/user/team/controller'; +import { MongoUser } from '@fastgpt/service/support/user/schema'; +import { UserModelSchema } from '@fastgpt/global/support/user/type'; +import { delay } from '@/utils/tools'; +import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; +import { + DatasetCollectionSchemaType, + DatasetSchemaType, + DatasetTrainingSchemaType +} from '@fastgpt/global/core/dataset/type'; +import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; +import { connectionMongo } from '@fastgpt/service/common/mongo'; +import { Types } from 'mongoose'; +import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; +import { PgClient } from '@fastgpt/service/common/pg'; +import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant'; +import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; +import { MongoOpenApi } from '@fastgpt/service/support/openapi/schema'; +import { MongoApp } from '@fastgpt/service/core/app/schema'; +import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; +import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; +import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; +import { POST } from '@fastgpt/service/common/api/plusRequest'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const { limit = 50, maxSize = 3 } = req.body as { limit: number; maxSize: number }; + await authCert({ req, authRoot: true }); + await connectToDatabase(); + + await initDefaultTeam(limit, maxSize); + await initMongoTeamId(limit); + await initDatasetAndApp(); + await initCollectionFileTeam(limit); + + if (global.systemEnv.pluginBaseUrl) { + POST('/admin/init46'); + } + + await initPgData(); + + jsonRes(res, { + data: {} + }); + } catch (error) { + console.log(error); + + jsonRes(res, { + code: 500, + error + }); + } +} + +async function initDefaultTeam(limit: number, maxSize: number) { + /* init user default Team */ + const users = await MongoUser.find({}, '_id balance'); + console.log('init user default team', users.length); + // 100 组一次 + const userArr: UserModelSchema[][] = []; + for (let i = 0; i < users.length; i += limit) { + userArr.push(users.slice(i, i + limit)); + } + let success = 0; + for await (const users of userArr) { + await Promise.all(users.map(init)); + success += limit; + console.log(success); + } + + async function init(user: UserModelSchema): Promise { + try { + await createDefaultTeam({ + userId: user._id, + balance: user.balance, + maxSize + }); + } catch (error) { + console.log(error); + + await delay(1000); + return init(user); + } + } +} +async function initMongoTeamId(limit: number) { + const mongoSchema = [ + { + label: 'MongoPlugin', + schema: MongoPlugin + }, + { + label: 'MongoChat', + schema: MongoChat + }, + { + label: 'MongoChatItem', + schema: MongoChatItem + }, + { + label: 'MongoApp', + schema: MongoApp + }, + { + label: 'MongoDataset', + schema: MongoDataset + }, + { + label: 'MongoDatasetCollection', + schema: MongoDatasetCollection + }, + { + label: 'MongoDatasetTraining', + schema: MongoDatasetTraining + }, + { + label: 'MongoBill', + schema: MongoBill + }, + { + label: 'MongoOutLink', + schema: MongoOutLink + }, + { + label: 'MongoOpenApi', + schema: MongoOpenApi + } + ]; + /* init user default Team */ + + for await (const item of mongoSchema) { + console.log('start init', item.label); + await initTeamTmbId(item.schema); + console.log('finish init', item.label); + } + + async function initTeamTmbId(schema: any) { + const emptyWhere = { + $or: [{ teamId: { $exists: false } }, { teamId: null }] + }; + const uniqueUsersWithNoTeamId = await schema.aggregate([ + { + $match: emptyWhere + }, + { + $group: { + _id: '$userId', // 按 userId 分组以去重 + userId: { $first: '$userId' } // 保留第一个出现的 userId + } + }, + { + $project: { + _id: 0, // 不显示 _id 字段 + userId: 1 // 只显示 userId 字段 + } + } + ]); + const users = uniqueUsersWithNoTeamId; + + console.log('un init total', users.length); + // limit 组一次 + const userArr: any[][] = []; + for (let i = 0; i < users.length; i += limit) { + userArr.push(users.slice(i, i + limit)); + } + + let success = 0; + for await (const users of userArr) { + await Promise.all(users.map((item) => init(item.userId))); + success += limit; + console.log(success); + } + + async function init(userId: string): Promise { + try { + const tmb = await getTeamInfoByTmbId({ userId }); + + await schema.updateMany( + { + userId, + ...emptyWhere + }, + { + teamId: tmb.teamId, + tmbId: tmb.tmbId + } + ); + } catch (error) { + if (error === 'team not exist' || error === 'tmbId or userId is required') { + return; + } + console.log(error); + await delay(1000); + return init(userId); + } + } + } +} +async function initDatasetAndApp() { + await MongoDataset.updateMany( + {}, + { + $set: { + permission: PermissionTypeEnum.private + } + } + ); + await MongoApp.updateMany( + {}, + { + $set: { + permission: PermissionTypeEnum.private + } + } + ); +} +async function initCollectionFileTeam(limit: number) { + /* init user default Team */ + const DatasetFile = connectionMongo.connection.db.collection(`dataset.files`); + const matchWhere = { + $or: [{ 'metadata.teamId': { $exists: false } }, { 'metadata.teamId': null }] + }; + const uniqueUsersWithNoTeamId = await DatasetFile.aggregate([ + { + $match: matchWhere + }, + { + $group: { + _id: '$metadata.userId', // 按 metadata.userId 分组以去重 + userId: { $first: '$metadata.userId' } // 保留第一个出现的 userId + } + }, + { + $project: { + _id: 0, // 不显示 _id 字段 + userId: 1 // 只显示 userId 字段 + } + } + ]).toArray(); + const users = uniqueUsersWithNoTeamId; + + console.log('un init total', users.length); + // limit 组一次 + const userArr: any[][] = []; + for (let i = 0; i < users.length; i += limit) { + userArr.push(users.slice(i, i + limit)); + } + + let success = 0; + for await (const item of userArr) { + await Promise.all(item.map((item) => init(item.userId))); + success += limit; + console.log(success); + } + + async function init(userId: string): Promise { + try { + const tmb = await getTeamInfoByTmbId({ + userId + }); + + await DatasetFile.updateMany( + { + userId, + ...matchWhere + }, + { + $set: { + 'metadata.teamId': String(tmb.teamId), + 'metadata.tmbId': String(tmb.tmbId) + } + } + ); + } catch (error) { + if (error === 'team not exist' || error === 'tmbId or userId is required') { + return; + } + console.log(error); + await delay(1000); + return init(userId); + } + } +} +async function initPgData() { + const limit = 10; + // add column + try { + await Promise.all([ + PgClient.query(`ALTER TABLE ${PgDatasetTableName} ADD COLUMN team_id CHAR(50);`), + PgClient.query(`ALTER TABLE ${PgDatasetTableName} ADD COLUMN tmb_id CHAR(50);`), + PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN user_id DROP NOT NULL;`) + ]); + } catch (error) { + console.log(error); + console.log('column exits'); + } + + const { rows } = await PgClient.query<{ user_id: string }>(` + SELECT DISTINCT user_id FROM ${PgDatasetTableName} WHERE team_id IS NULL; +`); + console.log('init pg', rows.length); + let success = 0; + for (let i = 0; i < limit; i++) { + init(i); + } + async function init(index: number): Promise { + const userId = rows[index]?.user_id; + if (!userId) return; + try { + const tmb = await getTeamInfoByTmbId({ userId }); + // update pg + await PgClient.query( + `Update ${PgDatasetTableName} set team_id = '${tmb.teamId}', tmb_id = '${tmb.tmbId}' where user_id = '${userId}' AND team_id IS NULL;` + ); + console.log(++success); + init(index + limit); + } catch (error) { + if (error === 'default team not exist') { + return; + } + console.log(error); + await delay(1000); + return init(index); + } + } +} diff --git a/projects/app/src/pages/api/app/myApps.ts b/projects/app/src/pages/api/app/myApps.ts deleted file mode 100644 index 5ba984899..000000000 --- a/projects/app/src/pages/api/app/myApps.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase, App } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { AppListItemType } from '@/types/app'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); - - // 根据 userId 获取模型信息 - const myApps = await App.find( - { - userId - }, - '_id avatar name intro' - ).sort({ - updateTime: -1 - }); - - jsonRes(res, { - data: myApps - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/chat/history/updateChatHistory.ts b/projects/app/src/pages/api/chat/history/updateChatHistory.ts deleted file mode 100644 index 73b81d047..000000000 --- a/projects/app/src/pages/api/chat/history/updateChatHistory.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase, Chat } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; - -export type Props = { - chatId: string; - customTitle?: string; - top?: boolean; -}; - -/* 更新聊天标题 */ -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { chatId, customTitle, top } = req.body as Props; - - const { userId } = await authUser({ req, authToken: true }); - - await Chat.findOneAndUpdate( - { - chatId, - userId - }, - { - ...(customTitle ? { customTitle } : {}), - ...(top ? { top } : { top: null }) - } - ); - jsonRes(res); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/chat/init.ts b/projects/app/src/pages/api/chat/init.ts deleted file mode 100644 index 16149888c..000000000 --- a/projects/app/src/pages/api/chat/init.ts +++ /dev/null @@ -1,110 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { Chat, ChatItem, connectToDatabase } from '@/service/mongo'; -import type { InitChatResponse } from '@/global/core/api/chatRes.d'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { ChatItemType } from '@/types/chat'; -import { authApp } from '@/service/utils/auth'; -import type { ChatSchema } from '@/types/mongoSchema'; -import { getGuideModule } from '@/global/core/app/modules/utils'; -import { getChatModelNameListByModules } from '@/service/core/app/module'; -import { TaskResponseKeyEnum } from '@/constants/chat'; - -/* 初始化我的聊天框,需要身份验证 */ -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); - - let { appId, chatId } = req.query as { - appId: '' | string; - chatId: '' | string; - }; - - if (!appId) { - return jsonRes(res, { - code: 501, - message: "You don't have an app yet" - }); - } - - // 校验使用权限 - const app = ( - await authApp({ - appId, - userId, - authUser: false, - authOwner: false - }) - ).app; - - // get app and history - const { chat, history = [] }: { chat?: ChatSchema; history?: ChatItemType[] } = - await (async () => { - if (chatId) { - // auth chatId - const [chat, history] = await Promise.all([ - Chat.findOne( - { - chatId, - userId, - appId - }, - 'title variables' - ), - ChatItem.find( - { - chatId, - userId, - appId - }, - `dataId obj value adminFeedback userFeedback ${TaskResponseKeyEnum.responseData}` - ) - .sort({ _id: -1 }) - .limit(30) - ]); - if (!chat) { - throw new Error('聊天框不存在'); - } - history.reverse(); - return { app, history, chat }; - } - return {}; - })(); - - if (!app) { - throw new Error('Auth App Error'); - } - - const isOwner = String(app.userId) === userId; - - jsonRes(res, { - data: { - chatId, - appId, - app: { - userGuideModule: getGuideModule(app.modules), - chatModels: getChatModelNameListByModules(app.modules), - name: app.name, - avatar: app.avatar, - intro: app.intro, - canUse: app.share.isShare || isOwner - }, - title: chat?.title || '新对话', - variables: chat?.variables || {}, - history - } - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} - -export const config = { - api: { - responseLimit: '10mb' - } -}; diff --git a/projects/app/src/pages/api/chat/removeHistory.ts b/projects/app/src/pages/api/chat/removeHistory.ts deleted file mode 100644 index 578c4ce44..000000000 --- a/projects/app/src/pages/api/chat/removeHistory.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase, Chat, ChatItem } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { ChatSourceEnum } from '@/constants/chat'; - -type Props = { - chatId?: string; - appId?: string; -}; - -/* clear chat history */ -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { chatId, appId } = req.query as Props; - const { userId } = await authUser({ req, authToken: true }); - - if (chatId) { - await Promise.all([ - Chat.findOneAndRemove({ - chatId, - userId - }), - ChatItem.deleteMany({ - userId, - chatId - }) - ]); - } - if (appId) { - const chats = await Chat.find({ - appId, - userId, - source: ChatSourceEnum.online - }).select('_id'); - const chatIds = chats.map((chat) => chat._id); - - await Promise.all([ - Chat.deleteMany({ - _id: { $in: chatIds } - }), - ChatItem.deleteMany({ - chatId: { $in: chatIds } - }) - ]); - } - - jsonRes(res); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/system/file/read.ts b/projects/app/src/pages/api/common/file/read.ts similarity index 69% rename from projects/app/src/pages/api/system/file/read.ts rename to projects/app/src/pages/api/common/file/read.ts index c4af0d3c5..57d3457b7 100644 --- a/projects/app/src/pages/api/system/file/read.ts +++ b/projects/app/src/pages/api/common/file/read.ts @@ -1,9 +1,9 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { GridFSStorage } from '@/service/lib/gridfs'; -import { authFileToken } from './readUrl'; +import { authFileToken } from '@fastgpt/service/support/permission/controller'; import jschardet from 'jschardet'; +import { getDownloadBuf, getFileById } from '@fastgpt/service/common/file/gridfs/controller'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -11,17 +11,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const { token } = req.query as { token: string }; - const { fileId, userId } = await authFileToken(token); + const { fileId, teamId, tmbId, bucketName } = await authFileToken(token); if (!fileId) { throw new Error('fileId is empty'); } - const gridFs = new GridFSStorage('dataset', userId); - const [file, buffer] = await Promise.all([ - gridFs.findAndAuthFile(fileId), - gridFs.download(fileId) + getFileById({ bucketName, fileId }), + getDownloadBuf({ bucketName, fileId }) ]); const encoding = jschardet.detect(buffer)?.encoding; diff --git a/projects/app/src/pages/api/system/file/upload.ts b/projects/app/src/pages/api/common/file/upload.ts similarity index 75% rename from projects/app/src/pages/api/system/file/upload.ts rename to projects/app/src/pages/api/common/file/upload.ts index d252308d7..8acdf9bcc 100644 --- a/projects/app/src/pages/api/system/file/upload.ts +++ b/projects/app/src/pages/api/common/file/upload.ts @@ -1,11 +1,12 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { GridFSStorage } from '@/service/lib/gridfs'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { customAlphabet } from 'nanoid'; import multer from 'multer'; import path from 'path'; +import { uploadFile } from '@fastgpt/service/common/file/gridfs/controller'; +import { BucketNameEnum } from '@fastgpt/global/common/file/constants'; const nanoid = customAlphabet('1234567890abcdef', 12); @@ -38,7 +39,11 @@ class UploadModel { }).any(); async doUpload(req: NextApiRequest, res: NextApiResponse) { - return new Promise<{ files: FileType[]; metadata: Record }>((resolve, reject) => { + return new Promise<{ + files: FileType[]; + bucketName: `${BucketNameEnum}`; + metadata: Record; + }>((resolve, reject) => { // @ts-ignore this.uploader(req, res, (error) => { if (error) { @@ -52,6 +57,7 @@ class UploadModel { ...file, originalname: decodeURIComponent(file.originalname) })) || [], + bucketName: req.body.bucketName, metadata: (() => { if (!req.body?.metadata) return {}; try { @@ -73,15 +79,16 @@ const upload = new UploadModel(); export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); + const { userId, teamId, tmbId } = await authCert({ req, authToken: true }); - const { files, metadata } = await upload.doUpload(req, res); - - const gridFs = new GridFSStorage('dataset', userId); + const { files, bucketName, metadata } = await upload.doUpload(req, res); const upLoadResults = await Promise.all( files.map((file) => - gridFs.save({ + uploadFile({ + teamId, + tmbId, + bucketName, path: file.path, filename: file.originalname, metadata: { diff --git a/projects/app/src/pages/api/system/file/uploadImage.ts b/projects/app/src/pages/api/common/file/uploadImage.ts similarity index 74% rename from projects/app/src/pages/api/system/file/uploadImage.ts rename to projects/app/src/pages/api/common/file/uploadImage.ts index 01a011275..92bac2d2e 100644 --- a/projects/app/src/pages/api/system/file/uploadImage.ts +++ b/projects/app/src/pages/api/common/file/uploadImage.ts @@ -1,7 +1,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { uploadMongoImg } from '@fastgpt/service/common/file/image/controller'; type Props = { base64Img: string }; @@ -9,7 +9,7 @@ type Props = { base64Img: string }; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); + const { userId } = await authCert({ req, authToken: true }); const { base64Img } = req.body as Props; const data = await uploadMongoImg({ diff --git a/projects/app/src/pages/api/user/account/paySuccess.ts b/projects/app/src/pages/api/common/system/unlockTask.ts similarity index 77% rename from projects/app/src/pages/api/user/account/paySuccess.ts rename to projects/app/src/pages/api/common/system/unlockTask.ts index c42f2feb1..1fae29b38 100644 --- a/projects/app/src/pages/api/user/account/paySuccess.ts +++ b/projects/app/src/pages/api/common/system/unlockTask.ts @@ -1,14 +1,14 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; import { startQueue } from '@/service/utils/tools'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); + const { userId } = await authCert({ req, authToken: true }); await unlockTask(userId); } catch (error) {} jsonRes(res); diff --git a/projects/app/src/pages/api/core/ai/agent/createQuestionGuide.ts b/projects/app/src/pages/api/core/ai/agent/createQuestionGuide.ts index 106863fa2..de561a1a8 100644 --- a/projects/app/src/pages/api/core/ai/agent/createQuestionGuide.ts +++ b/projects/app/src/pages/api/core/ai/agent/createQuestionGuide.ts @@ -1,27 +1,20 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import type { CreateQuestionGuideParams } from '@/global/core/api/aiReq.d'; -import { pushQuestionGuideBill } from '@/service/common/bill/push'; +import type { CreateQuestionGuideParams } from '@/global/core/ai/api.d'; +import { pushQuestionGuideBill } from '@/service/support/wallet/bill/push'; import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); const { messages } = req.body as CreateQuestionGuideParams; - const { user } = await authUser({ + const { tmbId, teamId } = await authCert({ req, - authOutLink: true, - authToken: true, - authApiKey: true, - authBalance: true + authToken: true }); - if (!user) { - throw new Error('user not found'); - } - const qgModel = global.qgModels[0]; const { result, tokens } = await createQuestionGuide({ @@ -35,7 +28,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< pushQuestionGuideBill({ tokens: tokens, - userId: user._id + teamId, + tmbId }); } catch (err) { jsonRes(res, { diff --git a/projects/app/src/pages/api/app/create.ts b/projects/app/src/pages/api/core/app/create.ts similarity index 57% rename from projects/app/src/pages/api/app/create.ts rename to projects/app/src/pages/api/core/app/create.ts index 43b5c7021..9831070c3 100644 --- a/projects/app/src/pages/api/app/create.ts +++ b/projects/app/src/pages/api/core/app/create.ts @@ -1,11 +1,11 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { App } from '@/service/models/app'; -import type { CreateAppParams } from '@/types/app'; -import { AppTypeEnum } from '@/constants/app'; +import type { CreateAppParams } from '@fastgpt/global/core/app/api.d'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; +import { MongoApp } from '@fastgpt/service/core/app/schema'; +import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -22,21 +22,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< } // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true }); // 上限校验 - const authCount = await App.countDocuments({ - userId + const authCount = await MongoApp.countDocuments({ + teamId }); if (authCount >= 50) { - throw new Error('上限 50 个应用'); + throw new Error('每个团队上限 50 个应用'); } // 创建模型 - const response = await App.create({ + const response = await MongoApp.create({ avatar, name, - userId, + teamId, + tmbId, modules, type }); diff --git a/projects/app/src/pages/api/app/data/totalUsage.ts b/projects/app/src/pages/api/core/app/data/totalUsage.ts similarity index 74% rename from projects/app/src/pages/api/app/data/totalUsage.ts rename to projects/app/src/pages/api/core/app/data/totalUsage.ts index 4f746da17..137c2edaa 100644 --- a/projects/app/src/pages/api/app/data/totalUsage.ts +++ b/projects/app/src/pages/api/core/app/data/totalUsage.ts @@ -1,16 +1,17 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase, Bill } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { Types } from '@fastgpt/service/common/mongo'; +import { MongoBill } from '@fastgpt/service/support/wallet/bill/schema'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); const { appId, start, end } = req.body as { appId: string; start: number; end: number }; - const { userId } = await authUser({ req, authToken: true }); + const { userId } = await authCert({ req, authToken: true }); - const result = await Bill.aggregate([ + const result = await MongoBill.aggregate([ { $match: { appId: new Types.ObjectId(appId), diff --git a/projects/app/src/pages/api/app/del.ts b/projects/app/src/pages/api/core/app/del.ts similarity index 59% rename from projects/app/src/pages/api/app/del.ts rename to projects/app/src/pages/api/core/app/del.ts index c7f26be37..ec1114804 100644 --- a/projects/app/src/pages/api/app/del.ts +++ b/projects/app/src/pages/api/core/app/del.ts @@ -1,9 +1,10 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { Chat, App, connectToDatabase } from '@/service/mongo'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; +import { MongoApp } from '@fastgpt/service/core/app/schema'; import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { authApp } from '@/service/utils/auth'; +import { authApp } from '@fastgpt/service/support/permission/auth/app'; /* 获取我的模型 */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -16,16 +17,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< } // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); - - // 验证是否是该用户的 app - await authApp({ - appId, - userId - }); + await authApp({ req, authToken: true, appId, per: 'owner' }); // 删除对应的聊天 - await Chat.deleteMany({ + await MongoChat.deleteMany({ appId }); @@ -35,9 +30,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< }); // 删除模型 - await App.deleteOne({ - _id: appId, - userId + await MongoApp.deleteOne({ + _id: appId }); jsonRes(res); diff --git a/projects/app/src/pages/api/app/detail.tsx b/projects/app/src/pages/api/core/app/detail.tsx similarity index 64% rename from projects/app/src/pages/api/app/detail.tsx rename to projects/app/src/pages/api/core/app/detail.tsx index acdb74bc4..78e9c8381 100644 --- a/projects/app/src/pages/api/app/detail.tsx +++ b/projects/app/src/pages/api/core/app/detail.tsx @@ -1,8 +1,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { authApp } from '@/service/utils/auth'; +import { authApp } from '@fastgpt/service/support/permission/auth/app'; /* 获取我的模型 */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -15,12 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< } // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); - - const { app } = await authApp({ - appId, - userId - }); + const { app } = await authApp({ req, authToken: true, appId, per: 'w' }); jsonRes(res, { data: app diff --git a/projects/app/src/pages/api/app/getChatLogs.ts b/projects/app/src/pages/api/core/app/getChatLogs.ts similarity index 85% rename from projects/app/src/pages/api/app/getChatLogs.ts rename to projects/app/src/pages/api/core/app/getChatLogs.ts index 0181182d5..6c21ed380 100644 --- a/projects/app/src/pages/api/app/getChatLogs.ts +++ b/projects/app/src/pages/api/core/app/getChatLogs.ts @@ -1,12 +1,13 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { Chat, connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; import type { PagingData } from '@/types'; import { AppLogsListItemType } from '@/types/app'; import { Types } from '@fastgpt/service/common/mongo'; import { addDays } from 'date-fns'; import type { GetAppChatLogsParams } from '@/global/core/api/appReq.d'; +import { authApp } from '@fastgpt/service/support/permission/auth/app'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -24,11 +25,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + const { teamId } = await authApp({ req, authToken: true, appId, per: 'w' }); const where = { appId: new Types.ObjectId(appId), - userId: new Types.ObjectId(userId), + teamId: new Types.ObjectId(teamId), updateTime: { $gte: new Date(dateStart), $lte: new Date(dateEnd) @@ -36,7 +37,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }; const [data, total] = await Promise.all([ - Chat.aggregate([ + MongoChat.aggregate([ { $match: where }, { $lookup: { @@ -84,6 +85,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) { $limit: pageSize }, { $project: { + _id: 1, id: '$chatId', title: 1, source: 1, @@ -94,7 +96,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } } ]), - Chat.countDocuments(where) + MongoChat.countDocuments(where) ]); jsonRes>(res, { diff --git a/projects/app/src/pages/api/core/app/list.ts b/projects/app/src/pages/api/core/app/list.ts new file mode 100644 index 000000000..b4b45deef --- /dev/null +++ b/projects/app/src/pages/api/core/app/list.ts @@ -0,0 +1,38 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { MongoApp } from '@fastgpt/service/core/app/schema'; +import { mongoRPermission } from '@fastgpt/global/support/permission/utils'; +import { AppListItemType } from '@fastgpt/global/core/app/type'; +import { authUserRole } from '@fastgpt/service/support/permission/auth/user'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + await connectToDatabase(); + // 凭证校验 + const { teamId, tmbId, teamOwner, role } = await authUserRole({ req, authToken: true }); + + // 根据 userId 获取模型信息 + const myApps = await MongoApp.find( + { ...mongoRPermission({ teamId, tmbId, role }) }, + '_id avatar name intro tmbId permission' + ).sort({ + updateTime: -1 + }); + jsonRes(res, { + data: myApps.map((app) => ({ + _id: app._id, + avatar: app.avatar, + name: app.name, + intro: app.intro, + isOwner: teamOwner || String(app.tmbId) === tmbId, + permission: app.permission + })) + }); + } catch (err) { + jsonRes(res, { + code: 500, + error: err + }); + } +} diff --git a/projects/app/src/pages/api/app/update.ts b/projects/app/src/pages/api/core/app/update.ts similarity index 62% rename from projects/app/src/pages/api/app/update.ts rename to projects/app/src/pages/api/core/app/update.ts index d96c63021..c2b1f9a3a 100644 --- a/projects/app/src/pages/api/app/update.ts +++ b/projects/app/src/pages/api/core/app/update.ts @@ -1,17 +1,16 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { App } from '@/service/models/app'; -import type { AppUpdateParams } from '@/types/app'; -import { authApp } from '@/service/utils/auth'; +import { MongoApp } from '@fastgpt/service/core/app/schema'; +import type { AppUpdateParams } from '@fastgpt/global/core/app/api'; +import { authApp } from '@fastgpt/service/support/permission/auth/app'; import { SystemOutputEnum } from '@/constants/app'; /* 获取我的模型 */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { name, avatar, type, share, intro, modules } = req.body as AppUpdateParams; + const { name, avatar, type, intro, modules, permission } = req.body as AppUpdateParams; const { appId } = req.query as { appId: string }; if (!appId) { @@ -19,28 +18,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< } // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); - - await authApp({ - appId, - userId - }); + await authApp({ req, authToken: true, appId, per: permission ? 'owner' : 'w' }); // 更新模型 - await App.updateOne( + await MongoApp.updateOne( { - _id: appId, - userId + _id: appId }, { name, type, avatar, intro, - ...(share && { - 'share.isShare': share.isShare, - 'share.isShareDetail': share.isShareDetail - }), + permission, ...(modules && { modules: modules.map((modules) => ({ ...modules, diff --git a/projects/app/src/pages/api/chat/chatTest.ts b/projects/app/src/pages/api/core/chat/chatTest.ts similarity index 68% rename from projects/app/src/pages/api/chat/chatTest.ts rename to projects/app/src/pages/api/core/chat/chatTest.ts index 6a6f285a1..0f17c3083 100644 --- a/projects/app/src/pages/api/chat/chatTest.ts +++ b/projects/app/src/pages/api/core/chat/chatTest.ts @@ -1,14 +1,15 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { sseErrRes } from '@/service/response'; -import { sseResponseEventEnum } from '@/constants/chat'; +import { sseErrRes } from '@fastgpt/service/common/response'; +import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant'; import { responseWrite } from '@fastgpt/service/common/response'; import type { ModuleItemType } from '@fastgpt/global/core/module/type.d'; -import { dispatchModules } from '@/pages/api/v1/chat/completions'; -import { pushChatBill } from '@/service/common/bill/push'; -import { BillSourceEnum } from '@/constants/user'; -import { ChatItemType } from '@/types/chat'; +import { pushChatBill } from '@/service/support/wallet/bill/push'; +import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants'; +import type { ChatItemType } from '@fastgpt/global/core/chat/type'; +import { authApp } from '@fastgpt/service/support/permission/auth/app'; +import { authUser } from '@/service/support/permission/auth/user'; +import { dispatchModules } from '@/service/moduleDispatch'; export type Props = { history: ChatItemType[]; @@ -39,17 +40,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } /* user auth */ - const { userId, user } = await authUser({ req, authToken: true, authBalance: true }); - - if (!user) { - throw new Error('user not found'); - } + const [{ teamId, tmbId }, { user }] = await Promise.all([ + authApp({ req, authToken: true, appId, per: 'r' }), + authUser({ + req, + authToken: true, + minBalance: 0 + }) + ]); /* start process */ const { responseData } = await dispatchModules({ res, modules: modules, variables, + teamId, + tmbId, user, params: { history, @@ -74,7 +80,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) pushChatBill({ appName, appId, - userId, + teamId, + tmbId, source: BillSourceEnum.fastgpt, response: responseData }); diff --git a/projects/app/src/pages/api/core/chat/delete.ts b/projects/app/src/pages/api/core/chat/delete.ts new file mode 100644 index 000000000..4a74dc3df --- /dev/null +++ b/projects/app/src/pages/api/core/chat/delete.ts @@ -0,0 +1,54 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; +import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; +import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; + +type Props = { + chatId?: string; + appId?: string; +}; + +/* clear chat history */ +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + await connectToDatabase(); + const { chatId, appId } = req.query as Props; + + const { tmbId } = await authCert({ req, authToken: true }); + + if (chatId) { + await MongoChatItem.deleteMany({ + chatId, + tmbId + }); + await MongoChat.findOneAndRemove({ + chatId, + tmbId + }); + } + if (appId) { + const chats = await MongoChat.find({ + appId, + tmbId, + source: ChatSourceEnum.online + }).select('_id'); + const chatIds = chats.map((chat) => chat._id); + await MongoChatItem.deleteMany({ + chatId: { $in: chatIds } + }); + await MongoChat.deleteMany({ + _id: { $in: chatIds } + }); + } + + jsonRes(res); + } catch (err) { + jsonRes(res, { + code: 500, + error: err + }); + } +} diff --git a/projects/app/src/pages/api/chat/feedback/adminUpdate.ts b/projects/app/src/pages/api/core/chat/feedback/adminUpdate.ts similarity index 61% rename from projects/app/src/pages/api/chat/feedback/adminUpdate.ts rename to projects/app/src/pages/api/core/chat/feedback/adminUpdate.ts index b6c91d130..aa4f8613d 100644 --- a/projects/app/src/pages/api/chat/feedback/adminUpdate.ts +++ b/projects/app/src/pages/api/core/chat/feedback/adminUpdate.ts @@ -1,8 +1,9 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase, ChatItem } from '@/service/mongo'; -import type { AdminUpdateFeedbackParams } from '@/global/core/api/chatReq.d'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import type { AdminUpdateFeedbackParams } from '@fastgpt/global/core/chat/api.d'; +import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; /* 初始化我的聊天框,需要身份验证 */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -14,9 +15,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) throw new Error('missing parameter'); } - const { userId } = await authUser({ req, authToken: true }); + const { userId } = await authCert({ req, authToken: true }); - await ChatItem.findOneAndUpdate( + await MongoChatItem.findOneAndUpdate( { userId, dataId: chatItemId diff --git a/projects/app/src/pages/api/chat/feedback/userUpdate.ts b/projects/app/src/pages/api/core/chat/feedback/userUpdate.ts similarity index 75% rename from projects/app/src/pages/api/chat/feedback/userUpdate.ts rename to projects/app/src/pages/api/core/chat/feedback/userUpdate.ts index 9c9991e38..eadec11d8 100644 --- a/projects/app/src/pages/api/chat/feedback/userUpdate.ts +++ b/projects/app/src/pages/api/core/chat/feedback/userUpdate.ts @@ -1,6 +1,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase, ChatItem } from '@/service/mongo'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; /* 初始化我的聊天框,需要身份验证 */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -15,7 +16,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) throw new Error('chatItemId is required'); } - await ChatItem.findOneAndUpdate( + await MongoChatItem.findOneAndUpdate( { dataId: chatItemId }, diff --git a/projects/app/src/pages/api/core/chat/init.ts b/projects/app/src/pages/api/core/chat/init.ts new file mode 100644 index 000000000..1e7864491 --- /dev/null +++ b/projects/app/src/pages/api/core/chat/init.ts @@ -0,0 +1,97 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; +import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; +import type { InitChatResponse } from '@fastgpt/global/core/chat/api.d'; +import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; +import { authApp } from '@fastgpt/service/support/permission/auth/app'; +import type { ChatSchema } from '@fastgpt/global/core/chat/type.d'; +import { getGuideModule } from '@/global/core/app/modules/utils'; +import { getChatModelNameListByModules } from '@/service/core/app/module'; +import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; +import { authChat } from '@fastgpt/service/support/permission/auth/chat'; + +/* 初始化我的聊天框,需要身份验证 */ +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + await connectToDatabase(); + + let { appId, chatId } = req.query as { + appId: string; + chatId: '' | string; + }; + + if (!appId) { + return jsonRes(res, { + code: 501, + message: "You don't have an app yet" + }); + } + + // 校验使用权限 + const [{ app }, autChatResult] = await Promise.all([ + authApp({ + req, + authToken: true, + appId, + per: 'r' + }), + chatId + ? authChat({ + req, + authToken: true, + chatId, + per: 'r' + }) + : undefined + ]); + + // get app and history + const { history = [] }: { history?: ChatItemType[] } = await (async () => { + if (chatId) { + // auth chatId + const history = await MongoChatItem.find( + { + chatId + }, + `dataId obj value adminFeedback userFeedback ${TaskResponseKeyEnum.responseData}` + ) + .sort({ _id: -1 }) + .limit(30); + + history.reverse(); + return { history }; + } + return {}; + })(); + + jsonRes(res, { + data: { + chatId, + appId, + app: { + userGuideModule: getGuideModule(app.modules), + chatModels: getChatModelNameListByModules(app.modules), + name: app.name, + avatar: app.avatar, + intro: app.intro + }, + title: autChatResult?.chat?.title || '新对话', + variables: autChatResult?.chat?.variables || {}, + history + } + }); + } catch (err) { + jsonRes(res, { + code: 500, + error: err + }); + } +} + +export const config = { + api: { + responseLimit: '10mb' + } +}; diff --git a/projects/app/src/pages/api/chat/delChatRecordByContentId.ts b/projects/app/src/pages/api/core/chat/item/delete.ts similarity index 52% rename from projects/app/src/pages/api/chat/delChatRecordByContentId.ts rename to projects/app/src/pages/api/core/chat/item/delete.ts index 62dede49d..c26945829 100644 --- a/projects/app/src/pages/api/chat/delChatRecordByContentId.ts +++ b/projects/app/src/pages/api/core/chat/item/delete.ts @@ -1,21 +1,19 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase, ChatItem } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; +import { authChat } from '@fastgpt/service/support/permission/auth/chat'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); const { chatId, contentId } = req.query as { chatId: string; contentId: string }; - // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + await authChat({ req, authToken: true, chatId, per: 'w' }); - // 删除一条数据库记录 - await ChatItem.deleteOne({ + await MongoChatItem.deleteOne({ dataId: contentId, - chatId, - userId + chatId }); jsonRes(res); diff --git a/projects/app/src/pages/api/core/chat/item/getSpeech.ts b/projects/app/src/pages/api/core/chat/item/getSpeech.ts new file mode 100644 index 000000000..068cec401 --- /dev/null +++ b/projects/app/src/pages/api/core/chat/item/getSpeech.ts @@ -0,0 +1,72 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; +import { GetChatSpeechProps } from '@/global/core/chat/api.d'; +import { text2Speech } from '@fastgpt/service/core/ai/audio/speech'; +import { pushAudioSpeechBill } from '@/service/support/wallet/bill/push'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { authType2BillSource } from '@/service/support/wallet/bill/utils'; + +/* +1. get tts from chatItem store +2. get tts from ai +3. save tts to chatItem store if chatItemId is provided +4. push bill +*/ + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + await connectToDatabase(); + const { chatItemId, ttsConfig, input } = req.body as GetChatSpeechProps; + + const { teamId, tmbId, authType } = await authCert({ req, authToken: true }); + + const chatItem = await (async () => { + if (!chatItemId) return null; + return await MongoChatItem.findOne( + { + dataId: chatItemId + }, + 'tts' + ); + })(); + + if (chatItem?.tts) { + return jsonRes(res, { + data: chatItem.tts + }); + } + + const { tts, model } = await text2Speech({ + model: ttsConfig.model, + voice: ttsConfig.voice, + input + }); + + (async () => { + if (!chatItem) return; + try { + chatItem.tts = tts; + await chatItem.save(); + } catch (error) {} + })(); + + jsonRes(res, { + data: tts + }); + + pushAudioSpeechBill({ + model: model, + textLength: input.length, + tmbId, + teamId, + source: authType2BillSource({ authType }) + }); + } catch (err) { + jsonRes(res, { + code: 500, + error: err + }); + } +} diff --git a/projects/app/src/pages/api/chat/history/getHistory.ts b/projects/app/src/pages/api/core/chat/list.ts similarity index 52% rename from projects/app/src/pages/api/chat/history/getHistory.ts rename to projects/app/src/pages/api/core/chat/list.ts index 6226c1d00..3a47e058d 100644 --- a/projects/app/src/pages/api/chat/history/getHistory.ts +++ b/projects/app/src/pages/api/core/chat/list.ts @@ -1,22 +1,23 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase, Chat } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import type { ChatHistoryItemType } from '@/types/chat'; -import { ChatSourceEnum } from '@/constants/chat'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; +import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d'; +import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; +import { authApp } from '@fastgpt/service/support/permission/auth/app'; /* 获取历史记录 */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { appId } = req.body as { appId?: string }; - const { userId } = await authUser({ req, authToken: true }); + const { appId } = req.body as { appId: string }; + const { tmbId } = await authApp({ req, authToken: true, appId, per: 'r' }); - const data = await Chat.find( + const data = await MongoChat.find( { - userId, - source: ChatSourceEnum.online, - ...(appId && { appId }) + appId, + tmbId, + source: ChatSourceEnum.online }, 'chatId title top customTitle appId updateTime' ) diff --git a/projects/app/src/pages/api/core/chat/update.ts b/projects/app/src/pages/api/core/chat/update.ts new file mode 100644 index 000000000..0d64c87c0 --- /dev/null +++ b/projects/app/src/pages/api/core/chat/update.ts @@ -0,0 +1,27 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { UpdateHistoryProps } from '@fastgpt/global/core/chat/api.d'; +import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; +import { authChat } from '@fastgpt/service/support/permission/auth/chat'; + +/* 更新聊天标题 */ +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + await connectToDatabase(); + const { chatId, customTitle, top } = req.body as UpdateHistoryProps; + + await authChat({ req, authToken: true, chatId }); + + await MongoChat.findByIdAndUpdate(chatId, { + ...(customTitle ? { customTitle } : {}), + ...(top ? { top } : { top: null }) + }); + jsonRes(res); + } catch (err) { + jsonRes(res, { + code: 500, + error: err + }); + } +} diff --git a/projects/app/src/pages/api/core/dataset/allDataset.ts b/projects/app/src/pages/api/core/dataset/allDataset.ts index 8ef77e193..4452adda8 100644 --- a/projects/app/src/pages/api/core/dataset/allDataset.ts +++ b/projects/app/src/pages/api/core/dataset/allDataset.ts @@ -1,28 +1,32 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; import { getVectorModel } from '@/service/core/ai/model'; -import type { DatasetsItemType } from '@/types/core/dataset'; +import type { DatasetItemType } from '@/types/core/dataset'; +import { mongoRPermission } from '@fastgpt/global/support/permission/utils'; +import { authUserRole } from '@fastgpt/service/support/permission/auth/user'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + const { teamId, tmbId, teamOwner, role } = await authUserRole({ req, authToken: true }); const datasets = await MongoDataset.find({ - userId, + ...mongoRPermission({ teamId, tmbId, role }), type: 'dataset' }); const data = datasets.map((item) => ({ ...item.toJSON(), - vectorModel: getVectorModel(item.vectorModel) + tags: item.tags.join(' '), + vectorModel: getVectorModel(item.vectorModel), + canWrite: String(item.tmbId) === tmbId, + isOwner: teamOwner || String(item.tmbId) === tmbId })); - jsonRes(res, { + jsonRes(res, { data }); } catch (err) { diff --git a/projects/app/src/pages/api/core/dataset/collection/create.ts b/projects/app/src/pages/api/core/dataset/collection/create.ts index 2df07c2e2..378f0534f 100644 --- a/projects/app/src/pages/api/core/dataset/collection/create.ts +++ b/projects/app/src/pages/api/core/dataset/collection/create.ts @@ -1,28 +1,35 @@ /* Create one dataset collection */ - import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; import type { CreateDatasetCollectionParams } from '@/global/core/api/datasetReq.d'; import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant'; import { getCollectionUpdateTime } from '@fastgpt/service/core/dataset/collection/utils'; +import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user'; +import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); + const body = req.body as CreateDatasetCollectionParams; - const { userId } = await authUser({ req, authToken: true }); - - const body = req.body || {}; + // auth. not visitor and dataset is public + const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true }); + await authDataset({ + req, + authToken: true, + datasetId: body.datasetId, + per: 'r' + }); jsonRes(res, { data: await createOneCollection({ ...body, - userId + teamId, + tmbId }) }); } catch (err) { @@ -39,11 +46,13 @@ export async function createOneCollection({ datasetId, type, metadata = {}, - userId -}: CreateDatasetCollectionParams & { userId: string }) { + teamId, + tmbId +}: CreateDatasetCollectionParams & { teamId: string; tmbId: string }) { const { _id } = await MongoDatasetCollection.create({ name, - userId, + teamId, + tmbId, datasetId, parentId: parentId || null, type, @@ -56,7 +65,8 @@ export async function createOneCollection({ await createDefaultCollection({ datasetId, parentId: _id, - userId + teamId, + tmbId }); } @@ -68,20 +78,23 @@ export function createDefaultCollection({ name = '手动录入', datasetId, parentId, - userId + teamId, + tmbId }: { name?: '手动录入' | '手动标注'; datasetId: string; parentId?: string; - userId: string; + teamId: string; + tmbId: string; }) { return MongoDatasetCollection.create({ name, - userId, + teamId, + tmbId, datasetId, parentId, type: DatasetCollectionTypeEnum.virtual, - updateTime: new Date('2000'), + updateTime: new Date('2099'), metadata: {} }); } diff --git a/projects/app/src/pages/api/core/dataset/collection/delById.ts b/projects/app/src/pages/api/core/dataset/collection/delById.ts index 79fd75374..7c971c9ab 100644 --- a/projects/app/src/pages/api/core/dataset/collection/delById.ts +++ b/projects/app/src/pages/api/core/dataset/collection/delById.ts @@ -1,12 +1,13 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; import { findCollectionAndChild } from '@fastgpt/service/core/dataset/collection/utils'; -import { delDataByCollectionId } from '@/service/core/dataset/data/utils'; +import { delDataByCollectionId } from '@/service/core/dataset/data/controller'; import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; -import { GridFSStorage } from '@/service/lib/gridfs'; +import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset'; +import { delFileById } from '@fastgpt/service/common/file/gridfs/controller'; +import { BucketNameEnum } from '@fastgpt/global/common/file/constants'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -18,37 +19,40 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< throw new Error('CollectionIdId is required'); } - // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + const { teamId } = await authDatasetCollection({ + req, + authToken: true, + collectionId, + per: 'w' + }); // find all delete id const collections = await findCollectionAndChild(collectionId, '_id metadata'); const delIdList = collections.map((item) => item._id); // delete pg data - await delDataByCollectionId({ userId, collectionIds: delIdList }); + await delDataByCollectionId({ collectionIds: delIdList }); // delete training data await MongoDatasetTraining.deleteMany({ datasetCollectionId: { $in: delIdList }, - userId + teamId }); // delete file - const gridFs = new GridFSStorage('dataset', userId); - const fileCollection = gridFs.Collection(); await Promise.all( - collections.map( - (item) => - //@ts-ignore - item.metadata?.fileId && fileCollection.findOneAndDelete({ _id: item.metadata.fileId }) - ) + collections.map((collection) => { + if (!collection.metadata?.fileId) return; + return delFileById({ + bucketName: BucketNameEnum.dataset, + fileId: collection.metadata.fileId + }); + }) ); // delete collection await MongoDatasetCollection.deleteMany({ - _id: { $in: delIdList }, - userId + _id: { $in: delIdList } }); jsonRes(res); diff --git a/projects/app/src/pages/api/core/dataset/collection/detail.ts b/projects/app/src/pages/api/core/dataset/collection/detail.ts index 51c371a2b..4705024d9 100644 --- a/projects/app/src/pages/api/core/dataset/collection/detail.ts +++ b/projects/app/src/pages/api/core/dataset/collection/detail.ts @@ -2,10 +2,10 @@ Get one dataset collection detail */ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; +import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset'; +import { DatasetCollectionItemType } from '@fastgpt/global/core/dataset/type'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -17,16 +17,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< } // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + const { collection, canWrite } = await authDatasetCollection({ + req, + authToken: true, + collectionId: id, + per: 'r' + }); - const collection = await MongoDatasetCollection.findOne({ _id: id, userId }).lean(); - - if (!collection) { - throw new Error('Collection not found'); - } - - jsonRes(res, { - data: collection + jsonRes(res, { + data: { + ...collection, + datasetId: collection.datasetId._id, + canWrite + } }); } catch (err) { jsonRes(res, { diff --git a/projects/app/src/pages/api/core/dataset/collection/list.ts b/projects/app/src/pages/api/core/dataset/collection/list.ts index b0f2b8f0b..48184259a 100644 --- a/projects/app/src/pages/api/core/dataset/collection/list.ts +++ b/projects/app/src/pages/api/core/dataset/collection/list.ts @@ -1,9 +1,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { DatasetTrainingCollectionName } from '@fastgpt/service/core/dataset/training/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; - import { Types } from '@fastgpt/service/common/mongo'; import type { DatasetCollectionsListItemType } from '@/global/core/dataset/response'; import type { GetDatasetCollectionsProps } from '@/global/core/api/datasetReq'; @@ -12,6 +10,8 @@ import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection import { countCollectionData } from '@/service/core/dataset/data/utils'; import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant'; import { startQueue } from '@/service/utils/tools'; +import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; +import { getTeamInfoByTmbId } from '@fastgpt/service/support/user/team/controller'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -28,11 +28,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< } = req.body as GetDatasetCollectionsProps; searchText = searchText?.replace(/'/g, ''); - // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + // auth dataset and get my role + const { tmbId } = await authDataset({ req, authToken: true, datasetId, per: 'r' }); + const { canWrite } = await getTeamInfoByTmbId({ tmbId }); const match = { - userId: new Types.ObjectId(userId), datasetId: new Types.ObjectId(datasetId), parentId: parentId ? new Types.ObjectId(parentId) : null, ...(selectFolder ? { type: DatasetCollectionTypeEnum.folder } : {}), @@ -43,6 +43,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< : {}) }; + // not count data amount if (simple) { const collections = await MongoDatasetCollection.find(match, '_id name type parentId') .sort({ @@ -57,7 +58,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< collections.map(async (item) => ({ ...item, dataAmount: 0, - trainingAmount: 0 + trainingAmount: 0, + canWrite // admin or owner can write })) ), total: await MongoDatasetCollection.countDocuments(match) @@ -65,7 +67,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< }); } - const collections = await MongoDatasetCollection.aggregate([ + const collections: DatasetCollectionsListItemType[] = await MongoDatasetCollection.aggregate([ { $match: match }, @@ -82,11 +84,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< $project: { _id: 1, parentId: 1, - fileId: 1, + tmbId: 1, name: 1, type: 1, updateTime: 1, - trainingAmount: { $size: '$trainings_amount' } + trainingAmount: { $size: '$trainings_amount' }, + metadata: 1 } }, { @@ -108,7 +111,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const data = await Promise.all( collections.map(async (item, i) => ({ ...item, - dataAmount: item.type === DatasetCollectionTypeEnum.folder ? undefined : counts[i] + dataAmount: item.type === DatasetCollectionTypeEnum.folder ? undefined : counts[i], + canWrite: String(item.tmbId) === tmbId || canWrite })) ); diff --git a/projects/app/src/pages/api/core/dataset/collection/paths.ts b/projects/app/src/pages/api/core/dataset/collection/paths.ts index d7e294455..4e4662ff4 100644 --- a/projects/app/src/pages/api/core/dataset/collection/paths.ts +++ b/projects/app/src/pages/api/core/dataset/collection/paths.ts @@ -1,9 +1,9 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import type { DatasetPathItemType } from '@/types/core/dataset'; import { getDatasetCollectionPaths } from '@fastgpt/service/core/dataset/collection/utils'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -11,10 +11,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const { parentId } = req.query as { parentId: string }; - const { userId } = await authUser({ req, authToken: true }); + if (!parentId) { + return jsonRes(res, { + data: [] + }); + } + + await authDatasetCollection({ req, authToken: true, collectionId: parentId, per: 'r' }); const paths = await getDatasetCollectionPaths({ - parentId, - userId + parentId }); jsonRes(res, { diff --git a/projects/app/src/pages/api/core/dataset/collection/update.ts b/projects/app/src/pages/api/core/dataset/collection/update.ts index 258510243..4b5c06416 100644 --- a/projects/app/src/pages/api/core/dataset/collection/update.ts +++ b/projects/app/src/pages/api/core/dataset/collection/update.ts @@ -1,10 +1,10 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; import type { UpdateDatasetCollectionParams } from '@/global/core/api/datasetReq.d'; import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; import { getCollectionUpdateTime } from '@fastgpt/service/core/dataset/collection/utils'; +import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -16,7 +16,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< } // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + await authDatasetCollection({ req, authToken: true, collectionId: id, per: 'w' }); const updateFields: Record = { ...(parentId !== undefined && { parentId: parentId || null }), @@ -28,15 +28,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< updateFields[`metadata.${key}`] = value; } - await MongoDatasetCollection.findOneAndUpdate( - { - _id: id, - userId - }, - { - $set: updateFields - } - ); + await MongoDatasetCollection.findByIdAndUpdate(id, { + $set: updateFields + }); jsonRes(res); } catch (err) { diff --git a/projects/app/src/pages/api/core/dataset/create.ts b/projects/app/src/pages/api/core/dataset/create.ts index 8c1b603c3..e3b0a4850 100644 --- a/projects/app/src/pages/api/core/dataset/create.ts +++ b/projects/app/src/pages/api/core/dataset/create.ts @@ -1,10 +1,10 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; import type { CreateDatasetParams } from '@/global/core/api/datasetReq.d'; import { createDefaultCollection } from './collection/create'; +import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -12,11 +12,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const { name, tags, avatar, vectorModel, parentId, type } = req.body as CreateDatasetParams; // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true }); const { _id } = await MongoDataset.create({ name, - userId, + teamId, + tmbId, tags, vectorModel, avatar, @@ -26,7 +27,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< await createDefaultCollection({ datasetId: _id, - userId + teamId, + tmbId }); jsonRes(res, { data: _id }); diff --git a/projects/app/src/pages/api/core/dataset/data/delDataById.ts b/projects/app/src/pages/api/core/dataset/data/delDataById.ts index bfe7a16f4..a32e9ba8d 100644 --- a/projects/app/src/pages/api/core/dataset/data/delDataById.ts +++ b/projects/app/src/pages/api/core/dataset/data/delDataById.ts @@ -1,10 +1,10 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { PgClient } from '@/service/pg'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { PgClient } from '@fastgpt/service/common/pg'; import { withNextCors } from '@fastgpt/service/common/middle/cors'; -import { PgDatasetTableName } from '@/constants/plugin'; +import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant'; import { connectToDatabase } from '@/service/mongo'; +import { authDatasetData } from '@/service/support/permission/auth/dataset'; export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -18,10 +18,10 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex } // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + await authDatasetData({ req, authToken: true, dataId, per: 'w' }); await PgClient.delete(PgDatasetTableName, { - where: [['user_id', userId], 'AND', ['id', dataId]] + where: [['id', dataId]] }); jsonRes(res); diff --git a/projects/app/src/pages/api/core/dataset/data/exportAll.ts b/projects/app/src/pages/api/core/dataset/data/exportAll.ts index 0b7f7c8d7..9af6d38d2 100644 --- a/projects/app/src/pages/api/core/dataset/data/exportAll.ts +++ b/projects/app/src/pages/api/core/dataset/data/exportAll.ts @@ -1,14 +1,14 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoUser } from '@fastgpt/service/support/user/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { PgDatasetTableName } from '@/constants/plugin'; +import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant'; import { findAllChildrenIds } from '../delete'; import QueryStream from 'pg-query-stream'; -import { PgClient } from '@/service/pg'; -import { addLog } from '@/service/utils/tools'; +import { PgClient, Pg } from '@fastgpt/service/common/pg'; +import { addLog } from '@fastgpt/service/common/mongo/controller'; import { responseWriteController } from '@fastgpt/service/common/response'; +import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -17,12 +17,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< datasetId: string; }; - if (!datasetId || !global.pgClient) { + if (!datasetId || !Pg) { throw new Error('缺少参数'); } // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + const { userId } = await authDataset({ req, authToken: true, datasetId, per: 'w' }); const exportIds = [datasetId, ...(await findAllChildrenIds(datasetId))]; @@ -48,7 +48,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< } const { rows } = await PgClient.query( - `SELECT count(id) FROM ${PgDatasetTableName} where user_id='${userId}' AND dataset_id IN (${exportIds + `SELECT count(id) FROM ${PgDatasetTableName} where dataset_id IN (${exportIds .map((id) => `'${id}'`) .join(',')})` ); @@ -61,7 +61,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< } // connect pg - global.pgClient.connect((err, client, done) => { + Pg.connect((err, client, done) => { if (err) { console.error(err); res.end('Error connecting to database'); @@ -71,7 +71,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< // create pg select stream const query = new QueryStream( - `SELECT q, a FROM ${PgDatasetTableName} where user_id='${userId}' AND dataset_id IN (${exportIds + `SELECT q, a FROM ${PgDatasetTableName} where dataset_id IN (${exportIds .map((id) => `'${id}'`) .join(',')})` ); diff --git a/projects/app/src/pages/api/core/dataset/data/getDataById.ts b/projects/app/src/pages/api/core/dataset/data/getDataById.ts index adb341278..dcdb41cf0 100644 --- a/projects/app/src/pages/api/core/dataset/data/getDataById.ts +++ b/projects/app/src/pages/api/core/dataset/data/getDataById.ts @@ -1,11 +1,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { PgClient } from '@/service/pg'; -import { PgDatasetTableName } from '@/constants/plugin'; -import type { DatasetDataItemType, PgDataItemType } from '@fastgpt/global/core/dataset/type'; -import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; +import { authDatasetData } from '@/service/support/permission/auth/dataset'; export type Response = { id: string; @@ -25,10 +21,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< } // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + const { datasetData } = await authDatasetData({ req, authToken: true, dataId, per: 'r' }); jsonRes(res, { - data: await getDatasetDataById({ userId, id: dataId }) + data: datasetData }); } catch (err) { jsonRes(res, { @@ -37,70 +33,3 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< }); } } - -export async function getDatasetDataById({ - id, - userId -}: { - id: string; - userId: string; -}): Promise { - const where: any = [['user_id', userId], 'AND', ['id', id]]; - - const searchRes = await PgClient.select(PgDatasetTableName, { - fields: ['id', 'q', 'a', 'dataset_id', 'collection_id'], - where, - limit: 1 - }); - - const data = searchRes?.rows?.[0]; - - if (!data) { - return Promise.reject('Data not found'); - } - - // find source - const collection = (await getDatasetDataItemInfo({ pgDataList: [data] }))[0]; - - if (!collection) { - return Promise.reject('Data Collection not found'); - } - - return { - id: data.id, - q: data.q, - a: data.a, - datasetId: data.dataset_id, - collectionId: data.collection_id, - sourceName: collection.sourceName, - sourceId: collection.sourceId - }; -} - -export async function getDatasetDataItemInfo({ - pgDataList -}: { - pgDataList: PgDataItemType[]; -}): Promise { - const collections = await MongoDatasetCollection.find( - { - _id: { $in: pgDataList.map((item) => item.collection_id) } - }, - '_id name datasetId metadata' - ).lean(); - - return pgDataList.map((item) => { - const collection = collections.find( - (collection) => String(collection._id) === item.collection_id - ); - return { - id: item.id, - q: item.q, - a: item.a, - datasetId: collection?.datasetId || '', - collectionId: item.collection_id, - sourceName: collection?.name || '', - sourceId: collection?.metadata?.fileId || collection?.metadata?.rawLink - }; - }); -} diff --git a/projects/app/src/pages/api/core/dataset/data/getDataList.ts b/projects/app/src/pages/api/core/dataset/data/getDataList.ts index bc6dee5df..50b963202 100644 --- a/projects/app/src/pages/api/core/dataset/data/getDataList.ts +++ b/projects/app/src/pages/api/core/dataset/data/getDataList.ts @@ -1,11 +1,11 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { PgClient } from '@/service/pg'; -import { PgDatasetTableName } from '@/constants/plugin'; +import { PgClient } from '@fastgpt/service/common/pg'; +import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant'; import type { DatasetDataListItemType } from '@/global/core/dataset/response.d'; import type { GetDatasetDataListProps } from '@/global/core/api/datasetReq'; +import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -16,18 +16,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< searchText = '', collectionId } = req.body as GetDatasetDataListProps; - if (!collectionId) { - throw new Error('collectionId is required'); - } // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + await authDatasetCollection({ req, authToken: true, collectionId, per: 'r' }); searchText = searchText.replace(/'/g, ''); const where: any = [ - ['user_id', userId], - 'AND', ['collection_id', collectionId], searchText ? `AND (q ILIKE '%${searchText}%' OR a ILIKE '%${searchText}%')` : '' ]; diff --git a/projects/app/src/pages/api/core/dataset/data/getQueueLen.ts b/projects/app/src/pages/api/core/dataset/data/getQueueLen.ts index 55116a563..3a0542cae 100644 --- a/projects/app/src/pages/api/core/dataset/data/getQueueLen.ts +++ b/projects/app/src/pages/api/core/dataset/data/getQueueLen.ts @@ -1,14 +1,14 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; /* 拆分数据成QA */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - await authUser({ req, authToken: true }); + await authCert({ req, authToken: true }); // split queue data const result = await MongoDatasetTraining.countDocuments({ diff --git a/projects/app/src/pages/api/core/dataset/data/insertData.ts b/projects/app/src/pages/api/core/dataset/data/insertData.ts index ce140078c..204c9ea4f 100644 --- a/projects/app/src/pages/api/core/dataset/data/insertData.ts +++ b/projects/app/src/pages/api/core/dataset/data/insertData.ts @@ -3,30 +3,87 @@ manual input or mark data */ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; import { withNextCors } from '@fastgpt/service/common/middle/cors'; import { SetOneDatasetDataProps } from '@/global/core/api/datasetReq'; -import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; -import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant'; -import { DatasetSchemaType } from '@fastgpt/global/core/dataset/type'; import { countPromptTokens } from '@/global/common/tiktoken'; import { getVectorModel } from '@/service/core/ai/model'; -import { insertData2Dataset, hasSameValue } from '@/service/core/dataset/data/utils'; +import { hasSameValue } from '@/service/core/dataset/data/utils'; +import { insertData2Dataset } from '@/service/core/dataset/data/controller'; +import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset'; +import { getCollectionWithDataset } from '@fastgpt/service/core/dataset/controller'; +import { authTeamBalance } from '@/service/support/permission/auth/bill'; +import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push'; export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); + const { collectionId, q, a } = req.body as SetOneDatasetDataProps; + + if (!q) { + return Promise.reject('q is required'); + } + + if (!collectionId) { + return Promise.reject('collectionId is required'); + } // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + const { teamId, tmbId } = await authDatasetCollection({ + req, + authToken: true, + authApiKey: true, + collectionId, + per: 'w' + }); + + await authTeamBalance(teamId); + + // auth collection and get dataset + const [ + { + datasetId: { _id: datasetId, vectorModel } + } + ] = await Promise.all([getCollectionWithDataset(collectionId), authTeamBalance(teamId)]); + + // format data + const formatQ = q.replace(/\\n/g, '\n').trim().replace(/'/g, '"'); + const formatA = a?.replace(/\\n/g, '\n').trim().replace(/'/g, '"') || ''; + + // token check + const token = countPromptTokens(formatQ, 'system'); + + if (token > getVectorModel(vectorModel).maxToken) { + return Promise.reject('Q Over Tokens'); + } + + // Duplicate data check + await hasSameValue({ + collectionId, + q: formatQ, + a: formatA + }); + + const { insertId, tokenLen } = await insertData2Dataset({ + teamId, + tmbId, + q: formatQ, + a: formatA, + collectionId, + datasetId, + model: vectorModel + }); + + pushGenerateVectorBill({ + teamId, + tmbId, + tokenLen: tokenLen, + model: vectorModel + }); jsonRes(res, { - data: await getVectorAndInsertDataset({ - ...req.body, - userId - }) + data: insertId }); } catch (err) { jsonRes(res, { @@ -35,61 +92,3 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex }); } }); - -export async function getVectorAndInsertDataset( - props: SetOneDatasetDataProps & { userId: string } -): Promise { - let { datasetId, collectionId, q, a, userId } = props; - - if (!datasetId) { - return Promise.reject('知识库 ID 不能为空'); - } - - if (!q) { - return Promise.reject('索引内容不能为空'); - } - - if (!collectionId) { - return Promise.reject('集合 ID 和集合类型不能同时为空'); - } - - // auth collection and get dataset - const collection = await MongoDatasetCollection.findOne({ - _id: collectionId, - userId, - datasetId, - type: { $ne: DatasetCollectionTypeEnum.folder } - }).populate('datasetId', '_id vectorModel'); - - if (!collection) { - return Promise.reject('集合不存在'); - } - const dataset = collection.datasetId as unknown as DatasetSchemaType; - - // format data - const formatQ = q?.replace(/\\n/g, '\n').trim().replace(/'/g, '"'); - const formatA = a?.replace(/\\n/g, '\n').trim().replace(/'/g, '"') || ''; - - // token check - const token = countPromptTokens(formatQ, 'system'); - - if (token > getVectorModel(dataset.vectorModel).maxToken) { - return Promise.reject('Q Over Tokens'); - } - - // Duplicate data check - await hasSameValue({ - collectionId, - q, - a - }); - - return insertData2Dataset({ - userId, - q: formatQ, - a: formatA, - collectionId, - datasetId, - model: dataset.vectorModel - }); -} diff --git a/projects/app/src/pages/api/core/dataset/data/pushData.ts b/projects/app/src/pages/api/core/dataset/data/pushData.ts index b13ce206f..8f02e5f50 100644 --- a/projects/app/src/pages/api/core/dataset/data/pushData.ts +++ b/projects/app/src/pages/api/core/dataset/data/pushData.ts @@ -1,10 +1,8 @@ /* push data to training queue */ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { authCollection } from '@fastgpt/service/core/dataset/auth'; import { withNextCors } from '@fastgpt/service/common/middle/cors'; import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constant'; import { startQueue } from '@/service/utils/tools'; @@ -13,6 +11,8 @@ import { countPromptTokens } from '@/global/common/tiktoken'; import type { PushDataResponse } from '@/global/core/api/datasetRes.d'; import type { PushDataProps } from '@/global/core/api/datasetReq.d'; import { getVectorModel } from '@/service/core/ai/model'; +import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset'; +import { getCollectionWithDataset } from '@fastgpt/service/core/dataset/controller'; const modeMap = { [TrainingModeEnum.index]: true, @@ -37,12 +37,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex } // 凭证校验 - const { userId } = await authUser({ req, authToken: true, authApiKey: true }); + const { teamId, tmbId } = await authDatasetCollection({ + req, + authToken: true, + authApiKey: true, + collectionId, + per: 'w' + }); jsonRes(res, { data: await pushDataToDatasetCollection({ ...req.body, - userId + teamId, + tmbId }) }); } catch (err) { @@ -54,20 +61,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex }); export async function pushDataToDatasetCollection({ - userId, + teamId, + tmbId, collectionId, data, mode, prompt, billId -}: { userId: string } & PushDataProps): Promise { - // auth dataset & get training model +}: { teamId: string; tmbId: string } & PushDataProps): Promise { + // get vector model const { - dataset: { _id: datasetId, vectorModel } - } = await authCollection({ - userId, - collectionId - }); + datasetId: { _id: datasetId, vectorModel } + } = await getCollectionWithDataset(collectionId); + const vectorModelData = getVectorModel(vectorModel); const modeMap = { @@ -76,7 +82,7 @@ export async function pushDataToDatasetCollection({ model: vectorModelData.model }, [TrainingModeEnum.qa]: { - maxToken: global.qaModels[0].maxToken * 0.8, + maxToken: global.qaModels[0].maxContext * 0.8, model: global.qaModels[0].model } }; @@ -119,7 +125,8 @@ export async function pushDataToDatasetCollection({ // 插入记录 const insertRes = await MongoDatasetTraining.insertMany( filterResult.success.map((item) => ({ - userId, + teamId, + tmbId, datasetId, datasetCollectionId: collectionId, billId, diff --git a/projects/app/src/pages/api/core/dataset/data/updateData.ts b/projects/app/src/pages/api/core/dataset/data/updateData.ts index ae613a787..0ddea06bd 100644 --- a/projects/app/src/pages/api/core/dataset/data/updateData.ts +++ b/projects/app/src/pages/api/core/dataset/data/updateData.ts @@ -1,39 +1,54 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { jsonRes } from '@fastgpt/service/common/response'; import { withNextCors } from '@fastgpt/service/common/middle/cors'; import { connectToDatabase } from '@/service/mongo'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; import type { SetOneDatasetDataProps } from '@/global/core/api/datasetReq.d'; -import { updateData2Dataset } from '@/service/core/dataset/data/utils'; +import { updateData2Dataset } from '@/service/core/dataset/data/controller'; +import { authDatasetData } from '@/service/support/permission/auth/dataset'; +import { authTeamBalance } from '@/service/support/permission/auth/bill'; +import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push'; export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { id, datasetId, collectionId, q = '', a } = req.body as SetOneDatasetDataProps; + const { id, collectionId, q = '', a } = req.body as SetOneDatasetDataProps; if (!id || !collectionId) { throw new Error('缺少参数'); } + // auth data permission + const { datasetData, teamId, tmbId } = await authDatasetData({ + req, + authToken: true, + dataId: id, + per: 'w' + }); + // auth team balance + await authTeamBalance(teamId); + // auth user and get kb - const [{ userId }, dataset] = await Promise.all([ - authUser({ req, authToken: true }), - MongoDataset.findById(datasetId, 'vectorModel') - ]); + const dataset = await MongoDataset.findById(datasetData.datasetId, 'vectorModel'); if (!dataset) { throw new Error("Can't find database"); } - await updateData2Dataset({ + const { tokenLen } = await updateData2Dataset({ dataId: id, - userId, q, a, model: dataset.vectorModel }); + pushGenerateVectorBill({ + teamId, + tmbId, + tokenLen: tokenLen, + model: dataset.vectorModel + }); + jsonRes(res); } catch (err) { jsonRes(res, { diff --git a/projects/app/src/pages/api/core/dataset/delete.ts b/projects/app/src/pages/api/core/dataset/delete.ts index 8a311d462..28f31a383 100644 --- a/projects/app/src/pages/api/core/dataset/delete.ts +++ b/projects/app/src/pages/api/core/dataset/delete.ts @@ -1,14 +1,14 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { PgClient } from '@/service/pg'; -import { PgDatasetTableName } from '@/constants/plugin'; -import { GridFSStorage } from '@/service/lib/gridfs'; +import { PgClient } from '@fastgpt/service/common/pg'; +import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant'; +import { delDatasetFiles } from '@fastgpt/service/core/dataset/file/controller'; import { Types } from '@fastgpt/service/common/mongo'; import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; +import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -21,29 +21,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< throw new Error('缺少参数'); } - // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + // auth owner + await authDataset({ req, authToken: true, datasetId: id, per: 'owner' }); const deletedIds = [id, ...(await findAllChildrenIds(id))]; // delete training data await MongoDatasetTraining.deleteMany({ - userId, datasetId: { $in: deletedIds.map((id) => new Types.ObjectId(id)) } }); // delete all pg data await PgClient.delete(PgDatasetTableName, { - where: [ - ['user_id', userId], - 'AND', - `dataset_id IN (${deletedIds.map((id) => `'${id}'`).join(',')})` - ] + where: [`dataset_id IN (${deletedIds.map((id) => `'${id}'`).join(',')})`] }); // delete related files - const gridFs = new GridFSStorage('dataset', userId); - await Promise.all(deletedIds.map((id) => gridFs.deleteFilesByDatasetId(id))); + await delDatasetFiles({ datasetId: id }); // delete collections await MongoDatasetCollection.deleteMany({ @@ -52,8 +46,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< // delete dataset data await MongoDataset.deleteMany({ - _id: { $in: deletedIds }, - userId + _id: { $in: deletedIds } }); jsonRes(res); diff --git a/projects/app/src/pages/api/core/dataset/detail.ts b/projects/app/src/pages/api/core/dataset/detail.ts index dc4def565..968d65a9e 100644 --- a/projects/app/src/pages/api/core/dataset/detail.ts +++ b/projects/app/src/pages/api/core/dataset/detail.ts @@ -1,41 +1,36 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; import { getVectorModel } from '@/service/core/ai/model'; -import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; +import { DatasetItemType } from '@/types/core/dataset'; +import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { id } = req.query as { + const { id: datasetId } = req.query as { id: string; }; - if (!id) { + if (!datasetId) { throw new Error('缺少参数'); } // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); - - const data = await MongoDataset.findOne({ - _id: id, - userId + const { dataset, canWrite, isOwner } = await authDataset({ + req, + authToken: true, + datasetId, + per: 'r' }); - if (!data) { - throw new Error('kb is not exist'); - } - - jsonRes(res, { + jsonRes(res, { data: { - _id: data._id, - avatar: data.avatar, - name: data.name, - userId: data.userId, - vectorModel: getVectorModel(data.vectorModel), - tags: data.tags.join(' ') + ...dataset, + tags: dataset.tags.join(' '), + vectorModel: getVectorModel(dataset.vectorModel), + canWrite, + isOwner } }); } catch (err) { diff --git a/projects/app/src/pages/api/core/dataset/file/delEmptyFiles.ts b/projects/app/src/pages/api/core/dataset/file/delEmptyFiles.ts deleted file mode 100644 index c22df97e5..000000000 --- a/projects/app/src/pages/api/core/dataset/file/delEmptyFiles.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { GridFSStorage } from '@/service/lib/gridfs'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - - const { datasetId } = req.query as { datasetId: string }; - // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); - - const gridFs = new GridFSStorage('dataset', userId); - const collection = gridFs.Collection(); - - const files = await collection.deleteMany({ - uploadDate: { $lte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) }, - ['metadata.datasetId']: datasetId, - ['metadata.userId']: userId, - ['metadata.datasetUsed']: { $ne: true } - }); - - jsonRes(res, { - data: files - }); - } catch (err) { - jsonRes(res); - } -} diff --git a/projects/app/src/pages/api/core/dataset/file/detail.ts b/projects/app/src/pages/api/core/dataset/file/detail.ts index df5035d1f..f8034ff08 100644 --- a/projects/app/src/pages/api/core/dataset/file/detail.ts +++ b/projects/app/src/pages/api/core/dataset/file/detail.ts @@ -1,14 +1,8 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { GridFSStorage } from '@/service/lib/gridfs'; -import { datasetSpecialIdMap } from '@fastgpt/global/core/dataset/constant'; -import { datasetSpecialIds } from '@fastgpt/global/core/dataset/constant'; -import type { GSFileInfoType } from '@/types/common/file'; -import { strIsLink } from '@fastgpt/global/common/string/tools'; -import { PgClient } from '@/service/pg'; -import { PgDatasetTableName } from '@/constants/plugin'; +import { authDatasetFile } from '@fastgpt/service/support/permission/auth/dataset'; +import { DatasetFileSchema } from '@fastgpt/global/core/dataset/type.d'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -16,46 +10,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const { fileId } = req.query as { fileId: string }; // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + const { file } = await authDatasetFile({ req, authToken: true, fileId, per: 'r' }); - // manual, mark - if (datasetSpecialIds.includes(fileId)) { - return jsonRes(res, { - data: { - id: fileId, - size: 0, - // @ts-ignore - filename: datasetSpecialIdMap[fileId]?.name || fileId, - uploadDate: new Date(), - encoding: '', - contentType: '' - } - }); - } - // link file - if (strIsLink(fileId)) { - const { rows } = await PgClient.select(PgDatasetTableName, { - where: [['user_id', userId], 'AND', ['file_id', fileId]], - limit: 1, - fields: ['source'] - }); - return jsonRes(res, { - data: { - id: fileId, - size: 0, - filename: rows[0]?.source || fileId, - uploadDate: new Date(), - encoding: '', - contentType: '' - } - }); - } - - const gridFs = new GridFSStorage('dataset', userId); - - const file = await gridFs.findAndAuthFile(fileId); - - jsonRes(res, { + jsonRes(res, { data: file }); } catch (err) { diff --git a/projects/app/src/pages/api/core/dataset/file/getPreviewUrl.ts b/projects/app/src/pages/api/core/dataset/file/getPreviewUrl.ts new file mode 100644 index 000000000..7b0eb8358 --- /dev/null +++ b/projects/app/src/pages/api/core/dataset/file/getPreviewUrl.ts @@ -0,0 +1,36 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { authDatasetFile } from '@fastgpt/service/support/permission/auth/dataset'; +import { createFileToken } from '@fastgpt/service/support/permission/controller'; +import { BucketNameEnum, FileBaseUrl } from '@fastgpt/global/common/file/constants'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + await connectToDatabase(); + + const { fileId } = req.query as { fileId: string }; + + if (!fileId) { + throw new Error('fileId is empty'); + } + + const { teamId, tmbId } = await authDatasetFile({ req, authToken: true, fileId, per: 'r' }); + + const token = await createFileToken({ + bucketName: BucketNameEnum.dataset, + teamId, + tmbId, + fileId + }); + + jsonRes(res, { + data: `${FileBaseUrl}?token=${token}` + }); + } catch (error) { + jsonRes(res, { + code: 500, + error + }); + } +} diff --git a/projects/app/src/pages/api/core/dataset/file/update.ts b/projects/app/src/pages/api/core/dataset/file/update.ts deleted file mode 100644 index 890cafa49..000000000 --- a/projects/app/src/pages/api/core/dataset/file/update.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { GridFSStorage } from '@/service/lib/gridfs'; -import { Types } from '@fastgpt/service/common/mongo'; -import { PgClient } from '@/service/pg'; -import { PgDatasetTableName } from '@/constants/plugin'; -import { addLog } from '@/service/utils/tools'; -import { strIsLink } from '@fastgpt/global/common/string/tools'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - - const { id, name, datasetUsed } = req.body; - const { userId } = await authUser({ req, authToken: true }); - - const gridFs = new GridFSStorage('dataset', userId); - const collection = gridFs.Collection(); - - if (id.length === 24 && !strIsLink(id)) { - await collection.findOneAndUpdate( - { - _id: new Types.ObjectId(id) - }, - { - $set: { - ...(name && { filename: name }), - ...(datasetUsed && { ['metadata.datasetUsed']: datasetUsed }) - } - } - ); - } - - // data source - await updateDatasetSource({ - fileId: id, - userId, - name - }); - - jsonRes(res, {}); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} -async function updateDatasetSource(data: { fileId: string; userId: string; name?: string }) { - const { fileId, userId, name } = data; - if (!fileId || !name || !userId) return; - try { - await PgClient.update(PgDatasetTableName, { - where: [['user_id', userId], 'AND', ['file_id', fileId]], - values: [ - { - key: 'source', - value: name - } - ] - }); - } catch (error) { - addLog.error(`Update dataset source error`, error); - setTimeout(() => { - updateDatasetSource(data); - }, 2000); - } -} diff --git a/projects/app/src/pages/api/core/dataset/list.ts b/projects/app/src/pages/api/core/dataset/list.ts index 28c6370fd..271f53abe 100644 --- a/projects/app/src/pages/api/core/dataset/list.ts +++ b/projects/app/src/pages/api/core/dataset/list.ts @@ -1,21 +1,25 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; import { getVectorModel } from '@/service/core/ai/model'; -import type { DatasetsItemType } from '@/types/core/dataset'; +import type { DatasetItemType } from '@/types/core/dataset'; import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; +import { mongoRPermission } from '@fastgpt/global/support/permission/utils'; +import { authUserRole } from '@fastgpt/service/support/permission/auth/user'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); const { parentId, type } = req.query as { parentId?: string; type?: `${DatasetTypeEnum}` }; // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + const { teamId, tmbId, teamOwner, role, canWrite } = await authUserRole({ + req, + authToken: true + }); const datasets = await MongoDataset.find({ - userId, + ...mongoRPermission({ teamId, tmbId, role }), ...(parentId !== undefined && { parentId: parentId || null }), ...(type && { type }) }).sort({ updateTime: -1 }); @@ -23,11 +27,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const data = await Promise.all( datasets.map(async (item) => ({ ...item.toJSON(), - vectorModel: getVectorModel(item.vectorModel) + tags: item.tags.join(' '), + vectorModel: getVectorModel(item.vectorModel), + canWrite, + isOwner: teamOwner || String(item.tmbId) === tmbId })) ); - jsonRes(res, { + jsonRes(res, { data }); } catch (err) { diff --git a/projects/app/src/pages/api/core/dataset/paths.ts b/projects/app/src/pages/api/core/dataset/paths.ts index 78e2f22cb..ec91f360f 100644 --- a/projects/app/src/pages/api/core/dataset/paths.ts +++ b/projects/app/src/pages/api/core/dataset/paths.ts @@ -1,8 +1,9 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; import type { DatasetPathItemType } from '@/types/core/dataset'; +import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -10,6 +11,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const { parentId } = req.query as { parentId: string }; + if (!parentId) { + return jsonRes(res, { + data: [] + }); + } + + await authDataset({ req, authToken: true, datasetId: parentId, per: 'r' }); + jsonRes(res, { data: await getParents(parentId) }); diff --git a/projects/app/src/pages/api/core/dataset/searchTest.ts b/projects/app/src/pages/api/core/dataset/searchTest.ts index 4cc929ef6..b55716b43 100644 --- a/projects/app/src/pages/api/core/dataset/searchTest.ts +++ b/projects/app/src/pages/api/core/dataset/searchTest.ts @@ -1,64 +1,67 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { PgClient } from '@/service/pg'; +import { jsonRes } from '@fastgpt/service/common/response'; import { withNextCors } from '@fastgpt/service/common/middle/cors'; -import { getVector } from '../../openapi/plugin/vector'; -import { PgDatasetTableName } from '@/constants/plugin'; -import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; import type { SearchTestProps } from '@/global/core/api/datasetReq.d'; import { connectToDatabase } from '@/service/mongo'; -import type { - SearchDataResponseItemType, - SearchDataResultItemType -} from '@fastgpt/global/core/dataset/type'; -import { getDatasetDataItemInfo } from './data/getDataById'; +import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type'; +import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; +import { authTeamBalance } from '@/service/support/permission/auth/bill'; +import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push'; +import { countModelPrice } from '@/service/support/wallet/bill/utils'; +import { searchDatasetData } from '@/service/core/dataset/data/utils'; +import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools'; +import { ModelTypeEnum } from '@/service/core/ai/model'; +import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants'; export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { datasetId, text } = req.body as SearchTestProps; + const { datasetId, text, limit = 20 } = req.body as SearchTestProps; if (!datasetId || !text) { throw new Error('缺少参数'); } - // 凭证校验 - const [{ userId }, dataset] = await Promise.all([ - authUser({ req, authToken: true, authApiKey: true }), - MongoDataset.findById(datasetId, 'vectorModel') - ]); - - if (!userId || !dataset) { - throw new Error('缺少用户ID'); - } - - const { vectors } = await getVector({ - model: dataset.vectorModel, - userId, - input: [text] + // auth dataset role + const { dataset, teamId, tmbId, apikey } = await authDataset({ + req, + authToken: true, + authApiKey: true, + datasetId, + per: 'r' }); - const results: any = await PgClient.query( - `BEGIN; - SET LOCAL hnsw.ef_search = ${global.systemEnv.pgHNSWEfSearch || 100}; - select id, q, a, dataset_id, collection_id, (vector <#> '[${ - vectors[0] - }]') * -1 AS score from ${PgDatasetTableName} where dataset_id='${datasetId}' AND user_id='${userId}' ORDER BY vector <#> '[${ - vectors[0] - }]' limit 12; - COMMIT;` - ); + // auth balance + await authTeamBalance(teamId); - const rows = results?.[2]?.rows as SearchDataResultItemType[]; + const { searchRes, tokenLen } = await searchDatasetData({ + text, + model: dataset.vectorModel, + limit: Math.min(limit, 50), + datasetIds: [datasetId] + }); - const collectionsData = await getDatasetDataItemInfo({ pgDataList: rows }); + // push bill + pushGenerateVectorBill({ + teamId, + tmbId, + tokenLen: tokenLen, + model: dataset.vectorModel, + source: apikey ? BillSourceEnum.api : BillSourceEnum.fastgpt + }); + if (apikey) { + updateApiKeyUsage({ + apikey, + usage: countModelPrice({ + model: dataset.vectorModel, + tokens: tokenLen, + type: ModelTypeEnum.vector + }) + }); + } jsonRes(res, { - data: collectionsData.map((item, index) => ({ - ...item, - score: rows[index].score - })) + data: searchRes }); } catch (err) { jsonRes(res, { diff --git a/projects/app/src/pages/api/core/dataset/update.ts b/projects/app/src/pages/api/core/dataset/update.ts index 498e3fe54..670f4092f 100644 --- a/projects/app/src/pages/api/core/dataset/update.ts +++ b/projects/app/src/pages/api/core/dataset/update.ts @@ -1,34 +1,32 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; import type { DatasetUpdateParams } from '@/global/core/api/datasetReq.d'; +import { authDataset } from '@fastgpt/service/support/permission/auth/dataset'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { id, parentId, name, avatar, tags } = req.body as DatasetUpdateParams; + const { id, parentId, name, avatar, tags, permission } = req.body as DatasetUpdateParams; if (!id) { throw new Error('缺少参数'); } // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); + await authDataset({ req, authToken: true, datasetId: id, per: 'owner' }); await MongoDataset.findOneAndUpdate( { - _id: id, - userId + _id: id }, { ...(parentId !== undefined && { parentId: parentId || null }), ...(name && { name }), ...(avatar && { avatar }), - ...(typeof tags === 'string' && { - tags: tags.split(' ').filter((item) => item) - }) + ...(tags && { tags }), + ...(permission && { permission }) } ); diff --git a/projects/app/src/pages/api/core/plugin/create.ts b/projects/app/src/pages/api/core/plugin/create.ts index 618724d56..c0f4ab035 100644 --- a/projects/app/src/pages/api/core/plugin/create.ts +++ b/projects/app/src/pages/api/core/plugin/create.ts @@ -1,19 +1,24 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { createOnePlugin } from '@fastgpt/service/core/plugin/controller'; import type { CreateOnePluginParams } from '@fastgpt/global/core/plugin/controller'; -import { defaultModules } from '@fastgpt/global/core/plugin/constants'; +import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user'; +import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); + const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true }); const body = req.body as CreateOnePluginParams; + const { _id } = await MongoPlugin.create({ + ...body, + teamId, + tmbId + }); + jsonRes(res, { - data: await createOnePlugin({ userId, modules: defaultModules, ...body }) + data: _id }); } catch (err) { jsonRes(res, { diff --git a/projects/app/src/pages/api/core/plugin/delete.ts b/projects/app/src/pages/api/core/plugin/delete.ts index bfdd6b234..c843dad39 100644 --- a/projects/app/src/pages/api/core/plugin/delete.ts +++ b/projects/app/src/pages/api/core/plugin/delete.ts @@ -1,18 +1,18 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { deleteOnePlugin } from '@fastgpt/service/core/plugin/controller'; +import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; +import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { id } = req.query as { id: string }; await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); + await authPluginCrud({ req, authToken: true, id, per: 'owner' }); - jsonRes(res, { - data: await deleteOnePlugin({ id, userId }) - }); + await MongoPlugin.findByIdAndRemove(id); + + jsonRes(res, {}); } catch (err) { jsonRes(res, { code: 500, diff --git a/projects/app/src/pages/api/core/plugin/detail.ts b/projects/app/src/pages/api/core/plugin/detail.ts index cd6b72d2e..112907ab0 100644 --- a/projects/app/src/pages/api/core/plugin/detail.ts +++ b/projects/app/src/pages/api/core/plugin/detail.ts @@ -1,17 +1,17 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { getOnePluginDetail } from '@fastgpt/service/core/plugin/controller'; +import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; +import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { id } = req.query as { id: string }; await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); + await authPluginCrud({ req, authToken: true, id, per: 'r' }); jsonRes(res, { - data: await getOnePluginDetail({ id, userId }) + data: await MongoPlugin.findOne({ id }) }); } catch (err) { jsonRes(res, { diff --git a/projects/app/src/pages/api/core/plugin/list.ts b/projects/app/src/pages/api/core/plugin/list.ts index 0fa9bbb24..ec965e825 100644 --- a/projects/app/src/pages/api/core/plugin/list.ts +++ b/projects/app/src/pages/api/core/plugin/list.ts @@ -1,16 +1,16 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { getUserPlugins } from '@fastgpt/service/core/plugin/controller'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); + const { teamId } = await authCert({ req, authToken: true }); jsonRes(res, { - data: await getUserPlugins({ userId }) + data: await MongoPlugin.find({ teamId }) }); } catch (err) { jsonRes(res, { diff --git a/projects/app/src/pages/api/core/plugin/moduleDetail.ts b/projects/app/src/pages/api/core/plugin/moduleDetail.ts index 5ff86c3c0..c27b49b2a 100644 --- a/projects/app/src/pages/api/core/plugin/moduleDetail.ts +++ b/projects/app/src/pages/api/core/plugin/moduleDetail.ts @@ -1,17 +1,17 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; import { getPluginModuleDetail } from '@fastgpt/service/core/plugin/controller'; +import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { id } = req.query as { id: string }; await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); + await authPluginCrud({ req, authToken: true, id, per: 'r' }); jsonRes(res, { - data: await getPluginModuleDetail({ id, userId }) + data: await getPluginModuleDetail({ id }) }); } catch (err) { jsonRes(res, { diff --git a/projects/app/src/pages/api/core/plugin/templateList.ts b/projects/app/src/pages/api/core/plugin/templateList.ts index ef01ae47a..ed25a5a4e 100644 --- a/projects/app/src/pages/api/core/plugin/templateList.ts +++ b/projects/app/src/pages/api/core/plugin/templateList.ts @@ -1,16 +1,16 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { getUserPlugins2Templates } from '@fastgpt/service/core/plugin/controller'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); + const { teamId } = await authCert({ req, authToken: true }); jsonRes(res, { - data: await getUserPlugins2Templates({ userId }) + data: await getUserPlugins2Templates({ teamId }) }); } catch (err) { jsonRes(res, { diff --git a/projects/app/src/pages/api/core/plugin/update.ts b/projects/app/src/pages/api/core/plugin/update.ts index cca0cbbb1..20279c3b6 100644 --- a/projects/app/src/pages/api/core/plugin/update.ts +++ b/projects/app/src/pages/api/core/plugin/update.ts @@ -1,18 +1,19 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { updateOnePlugin } from '@fastgpt/service/core/plugin/controller'; import type { UpdatePluginParams } from '@fastgpt/global/core/plugin/controller'; +import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin'; +import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); - const body = req.body as UpdatePluginParams; + const { id, ...props } = req.body as UpdatePluginParams; + + await authPluginCrud({ req, authToken: true, id, per: 'owner' }); jsonRes(res, { - data: await updateOnePlugin({ userId, ...body }) + data: await MongoPlugin.findByIdAndUpdate(id, props) }); } catch (err) { jsonRes(res, { diff --git a/projects/app/src/pages/api/openapi/plugin/vector.ts b/projects/app/src/pages/api/openapi/plugin/vector.ts deleted file mode 100644 index 345fa7e71..000000000 --- a/projects/app/src/pages/api/openapi/plugin/vector.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { authBalanceByUid, authUser } from '@fastgpt/service/support/user/auth'; -import { withNextCors } from '@fastgpt/service/common/middle/cors'; -import { getAIApi } from '@fastgpt/service/core/ai/config'; -import { pushGenerateVectorBill } from '@/service/common/bill/push'; -import { connectToDatabase } from '@/service/mongo'; - -type Props = { - model: string; - input: string[]; - billId?: string; -}; -type Response = { - tokenLen: number; - vectors: number[][]; -}; - -export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); - let { input, model } = req.query as Props; - - if (!Array.isArray(input)) { - throw new Error('缺少参数'); - } - - jsonRes(res, { - data: await getVector({ userId, input, model }) - }); - } catch (err) { - console.log(err); - jsonRes(res, { - code: 500, - error: err - }); - } -}); - -export async function getVector({ - model = 'text-embedding-ada-002', - userId, - input, - billId -}: { userId?: string } & Props) { - try { - userId && (await authBalanceByUid(userId)); - - for (let i = 0; i < input.length; i++) { - if (!input[i]) { - return Promise.reject({ - code: 500, - message: '向量生成模块输入内容为空' - }); - } - } - - // 获取 chatAPI - const ai = getAIApi(); - - // 把输入的内容转成向量 - const result = await ai.embeddings - .create( - { - model, - input - }, - { - timeout: 60000 - } - ) - .then(async (res) => { - if (!res.data) { - return Promise.reject('Embedding API 404'); - } - if (!res?.data?.[0]?.embedding) { - console.log(res?.data); - // @ts-ignore - return Promise.reject(res.data?.err?.message || 'Embedding API Error'); - } - return { - tokenLen: res.usage.total_tokens || 0, - vectors: await Promise.all(res.data.map((item) => unityDimensional(item.embedding))) - }; - }); - - userId && - pushGenerateVectorBill({ - userId, - tokenLen: result.tokenLen, - model, - billId - }); - - return result; - } catch (error) { - console.log(`Embedding Error`, error); - - return Promise.reject(error); - } -} - -function unityDimensional(vector: number[]) { - if (vector.length > 1536) return Promise.reject('向量维度不能超过 1536'); - let resultVector = vector; - const vectorLen = vector.length; - - const zeroVector = new Array(1536 - vectorLen).fill(0); - - return resultVector.concat(zeroVector); -} diff --git a/projects/app/src/pages/api/plugins/urlFetch.ts b/projects/app/src/pages/api/plugins/urlFetch.ts index 8438a3819..05ec958a3 100644 --- a/projects/app/src/pages/api/plugins/urlFetch.ts +++ b/projects/app/src/pages/api/plugins/urlFetch.ts @@ -3,8 +3,8 @@ import { NextApiRequest, NextApiResponse } from 'next'; import axios from 'axios'; import { JSDOM } from 'jsdom'; import { Readability } from '@mozilla/readability'; -import { jsonRes } from '@/service/response'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; import type { FetchResultItem } from '@fastgpt/global/common/plugin/types/pluginRes.d'; import { simpleText } from '@fastgpt/global/common/string/tools'; import { connectToDatabase } from '@/service/mongo'; @@ -20,7 +20,7 @@ const fetchContent = async (req: NextApiRequest, res: NextApiResponse) => { throw new Error('urlList is empty'); } - await authUser({ req, authToken: true }); + await authCert({ req, authToken: true }); urlList = urlList.filter((url) => /^(http|https):\/\/[^ "]+$/.test(url)); diff --git a/projects/app/src/pages/api/plusApi/[...path].ts b/projects/app/src/pages/api/plusApi/[...path].ts index 065001570..455426084 100644 --- a/projects/app/src/pages/api/plusApi/[...path].ts +++ b/projects/app/src/pages/api/plusApi/[...path].ts @@ -1,8 +1,9 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { request } from '@fastgpt/service/common/api/plusRequest'; import type { Method } from 'axios'; import { connectToDatabase } from '@/service/mongo'; +import { setCookie } from '@fastgpt/service/support/permission/controller'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -32,6 +33,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) method ); + /* special response */ + // response cookie + if (repose?.cookie) { + setCookie(res, repose.cookie); + + return jsonRes(res, { + data: repose?.cookie + }); + } + jsonRes(res, { data: repose }); diff --git a/projects/app/src/pages/api/support/openapi/postKey.ts b/projects/app/src/pages/api/support/openapi/create.ts similarity index 72% rename from projects/app/src/pages/api/support/openapi/postKey.ts rename to projects/app/src/pages/api/support/openapi/create.ts index 5f43467b5..1e8a7a8d9 100644 --- a/projects/app/src/pages/api/support/openapi/postKey.ts +++ b/projects/app/src/pages/api/support/openapi/create.ts @@ -1,19 +1,19 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoOpenApi } from '@fastgpt/service/support/openapi/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; import { customAlphabet } from 'nanoid'; -import type { EditApiKeyProps } from '@/global/support/api/openapiReq.d'; +import type { EditApiKeyProps } from '@/global/support/openapi/api'; +import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); const { appId, name, limit } = req.body as EditApiKeyProps; - const { userId } = await authUser({ req, authToken: true }); + const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true }); - const count = await MongoOpenApi.find({ userId, appId }).countDocuments(); + const count = await MongoOpenApi.find({ tmbId, appId }).countDocuments(); if (count >= 10) { throw new Error('最多 10 组 API 秘钥'); @@ -26,7 +26,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const apiKey = `${global.systemEnv?.openapiPrefix || 'fastgpt'}-${nanoid()}`; await MongoOpenApi.create({ - userId, + teamId, + tmbId, apiKey, appId, name, diff --git a/projects/app/src/pages/api/support/openapi/delKey.ts b/projects/app/src/pages/api/support/openapi/delete.ts similarity index 68% rename from projects/app/src/pages/api/support/openapi/delKey.ts rename to projects/app/src/pages/api/support/openapi/delete.ts index f4828032b..31ea201d7 100644 --- a/projects/app/src/pages/api/support/openapi/delKey.ts +++ b/projects/app/src/pages/api/support/openapi/delete.ts @@ -1,9 +1,9 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoOpenApi } from '@fastgpt/service/support/openapi/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { authOpenApiKeyCrud } from '@fastgpt/service/support/permission/auth/openapi'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -14,9 +14,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) throw new Error('缺少参数'); } - const { userId } = await authUser({ req, authToken: true }); + await authOpenApiKeyCrud({ req, authToken: true, id, per: 'owner' }); - await MongoOpenApi.findOneAndRemove({ _id: id, userId }); + await MongoOpenApi.findOneAndRemove({ _id: id }); jsonRes(res); } catch (err) { diff --git a/projects/app/src/pages/api/support/openapi/getKeys.ts b/projects/app/src/pages/api/support/openapi/getKeys.ts deleted file mode 100644 index 0d1144cfd..000000000 --- a/projects/app/src/pages/api/support/openapi/getKeys.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase } from '@/service/mongo'; -import { MongoOpenApi } from '@fastgpt/service/support/openapi/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import type { GetApiKeyProps } from '@/global/support/api/openapiReq.d'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { appId } = req.query as GetApiKeyProps; - const { userId } = await authUser({ req, authToken: true }); - - const findResponse = await MongoOpenApi.find({ userId, appId }).sort({ _id: -1 }); - - jsonRes(res, { - data: findResponse.map((item) => item.toObject()) - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/support/openapi/list.ts b/projects/app/src/pages/api/support/openapi/list.ts new file mode 100644 index 000000000..03e6a2cae --- /dev/null +++ b/projects/app/src/pages/api/support/openapi/list.ts @@ -0,0 +1,48 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { MongoOpenApi } from '@fastgpt/service/support/openapi/schema'; +import type { GetApiKeyProps } from '@/global/support/openapi/api'; +import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user'; +import { authApp } from '@fastgpt/service/support/permission/auth/app'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + await connectToDatabase(); + const { appId } = req.query as GetApiKeyProps; + + if (appId) { + const { tmbId, teamOwner } = await authApp({ req, authToken: true, appId, per: 'w' }); + + const findResponse = await MongoOpenApi.find({ + appId, + ...(!teamOwner && { tmbId }) + }).sort({ _id: -1 }); + + return jsonRes(res, { + data: findResponse.map((item) => item.toObject()) + }); + } + + const { + teamId, + tmbId, + isOwner: teamOwner + } = await authUserNotVisitor({ req, authToken: true }); + + const findResponse = await MongoOpenApi.find({ + appId, + teamId, + ...(!teamOwner && { tmbId }) + }).sort({ _id: -1 }); + + return jsonRes(res, { + data: findResponse.map((item) => item.toObject()) + }); + } catch (err) { + jsonRes(res, { + code: 500, + error: err + }); + } +} diff --git a/projects/app/src/pages/api/support/openapi/putKey.ts b/projects/app/src/pages/api/support/openapi/update.ts similarity index 54% rename from projects/app/src/pages/api/support/openapi/putKey.ts rename to projects/app/src/pages/api/support/openapi/update.ts index 2253c26e1..9be89e34a 100644 --- a/projects/app/src/pages/api/support/openapi/putKey.ts +++ b/projects/app/src/pages/api/support/openapi/update.ts @@ -1,26 +1,21 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoOpenApi } from '@fastgpt/service/support/openapi/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import type { EditApiKeyProps } from '@/global/support/api/openapiReq.d'; +import type { EditApiKeyProps } from '@/global/support/openapi/api.d'; +import { authOpenApiKeyCrud } from '@fastgpt/service/support/permission/auth/openapi'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); const { _id, name, limit } = req.body as EditApiKeyProps & { _id: string }; - const { userId } = await authUser({ req, authToken: true }); - await MongoOpenApi.findOneAndUpdate( - { - _id, - userId - }, - { - ...(name && { name }), - ...(limit && { limit }) - } - ); + await authOpenApiKeyCrud({ req, authToken: true, id: _id, per: 'owner' }); + + await MongoOpenApi.findByIdAndUpdate(_id, { + ...(name && { name }), + ...(limit && { limit }) + }); jsonRes(res); } catch (err) { diff --git a/projects/app/src/pages/api/support/outLink/create.ts b/projects/app/src/pages/api/support/outLink/create.ts index 2d627ee5c..10f684abf 100644 --- a/projects/app/src/pages/api/support/outLink/create.ts +++ b/projects/app/src/pages/api/support/outLink/create.ts @@ -1,9 +1,8 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; -import { authApp } from '@/service/utils/auth'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { authApp } from '@fastgpt/service/support/permission/auth/app'; import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; import { customAlphabet } from 'nanoid'; import { OutLinkTypeEnum } from '@fastgpt/global/support/outLink/constant'; @@ -18,17 +17,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) type: `${OutLinkTypeEnum}`; }; - const { userId } = await authUser({ req, authToken: true }); - await authApp({ - appId, - userId, - authOwner: false - }); + const { teamId, tmbId } = await authApp({ req, authToken: true, appId, per: 'w' }); const shareId = nanoid(); await MongoOutLink.create({ shareId, - userId, + teamId, + tmbId, appId, ...props }); diff --git a/projects/app/src/pages/api/support/outLink/delete.ts b/projects/app/src/pages/api/support/outLink/delete.ts index 62df982f4..944ed86c5 100644 --- a/projects/app/src/pages/api/support/outLink/delete.ts +++ b/projects/app/src/pages/api/support/outLink/delete.ts @@ -1,8 +1,8 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { authOutLinkCrud } from '@fastgpt/service/support/permission/auth/outLink'; /* delete a shareChat by shareChatId */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -13,12 +13,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) id: string; }; - const { userId } = await authUser({ req, authToken: true }); + await authOutLinkCrud({ req, outLinkId: id, authToken: true, per: 'owner' }); - await MongoOutLink.findOneAndRemove({ - _id: id, - userId - }); + await MongoOutLink.findByIdAndRemove(id); jsonRes(res); } catch (err) { diff --git a/projects/app/src/pages/api/support/outLink/init.ts b/projects/app/src/pages/api/support/outLink/init.ts index 86f89c66d..1e68bb17d 100644 --- a/projects/app/src/pages/api/support/outLink/init.ts +++ b/projects/app/src/pages/api/support/outLink/init.ts @@ -1,14 +1,13 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; -import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; import { MongoUser } from '@fastgpt/service/support/user/schema'; -import type { InitShareChatResponse } from '@/global/support/api/outLinkRes.d'; -import { authApp } from '@/service/utils/auth'; -import { HUMAN_ICON } from '@/constants/chat'; +import type { InitShareChatResponse } from '@fastgpt/global/support/outLink/api.d'; +import { HUMAN_ICON } from '@fastgpt/global/core/chat/constants'; import { getGuideModule } from '@/global/core/app/modules/utils'; -import { authShareChatInit } from '@fastgpt/service/support/outLink/auth'; +import { authShareChatInit } from '@/service/support/outLink/auth'; import { getChatModelNameListByModules } from '@/service/core/app/module'; +import { authOutLinkValid } from '@fastgpt/service/support/permission/auth/outLink'; /* init share chat window */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -19,27 +18,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) authToken?: string; }; - if (!shareId) { - throw new Error('params is error'); - } - // get shareChat - const shareChat = await MongoOutLink.findOne({ shareId }); - - if (!shareChat) { - return jsonRes(res, { - code: 501, - error: '分享链接已失效' - }); - } + const { app, shareChat } = await authOutLinkValid({ shareId }); // 校验使用权限 - const [{ app }, user] = await Promise.all([ - authApp({ - appId: shareChat.appId, - userId: String(shareChat.userId), - authOwner: false - }), + const [user] = await Promise.all([ MongoUser.findById(shareChat.userId, 'avatar'), authShareChatInit({ authToken, diff --git a/projects/app/src/pages/api/support/outLink/list.ts b/projects/app/src/pages/api/support/outLink/list.ts index 5b111b57e..c3e7697a0 100644 --- a/projects/app/src/pages/api/support/outLink/list.ts +++ b/projects/app/src/pages/api/support/outLink/list.ts @@ -1,8 +1,8 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { authApp } from '@fastgpt/service/support/permission/auth/app'; /* get shareChat list by appId */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -13,11 +13,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) appId: string; }; - const { userId } = await authUser({ req, authToken: true }); + const { teamId, tmbId, isOwner } = await authApp({ req, authToken: true, appId, per: 'w' }); const data = await MongoOutLink.find({ appId, - userId + ...(isOwner ? { teamId } : { tmbId }) }).sort({ _id: -1 }); diff --git a/projects/app/src/pages/api/support/outLink/update.ts b/projects/app/src/pages/api/support/outLink/update.ts index 9ad7e341d..d5248b93f 100644 --- a/projects/app/src/pages/api/support/outLink/update.ts +++ b/projects/app/src/pages/api/support/outLink/update.ts @@ -1,8 +1,9 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; +import { authOutLinkCrud } from '@fastgpt/service/support/permission/auth/outLink'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -10,6 +11,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const { _id, name, responseDetail, limit } = req.body as OutLinkEditType & {}; + if (!_id) { + throw new Error('_id is required'); + } + + await authOutLinkCrud({ req, outLinkId: _id, authToken: true, per: 'owner' }); + await MongoOutLink.findByIdAndUpdate(_id, { name, responseDetail, diff --git a/projects/app/src/pages/api/common/bill/createTrainingBill.ts b/projects/app/src/pages/api/support/wallet/bill/createTrainingBill.ts similarity index 53% rename from projects/app/src/pages/api/common/bill/createTrainingBill.ts rename to projects/app/src/pages/api/support/wallet/bill/createTrainingBill.ts index 57e98d666..ffe270e40 100644 --- a/projects/app/src/pages/api/common/bill/createTrainingBill.ts +++ b/projects/app/src/pages/api/support/wallet/bill/createTrainingBill.ts @@ -1,21 +1,23 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase, Bill } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { BillSourceEnum } from '@/constants/user'; -import { CreateTrainingBillType } from '@fastgpt/global/common/bill/types/billReq.d'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { connectToDatabase } from '@/service/mongo'; +import { MongoBill } from '@fastgpt/service/support/wallet/bill/schema'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants'; +import { CreateTrainingBillProps } from '@fastgpt/global/support/wallet/bill/api.d'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { name } = req.body as CreateTrainingBillType; + const { name } = req.body as CreateTrainingBillProps; - const { userId } = await authUser({ req, authToken: true, authApiKey: true }); + const { teamId, tmbId } = await authCert({ req, authToken: true, authApiKey: true }); const qaModel = global.qaModels[0]; - const { _id } = await Bill.create({ - userId, + const { _id } = await MongoBill.create({ + teamId, + tmbId, appName: name, source: BillSourceEnum.training, list: [ diff --git a/projects/app/src/pages/api/system/file/delete.ts b/projects/app/src/pages/api/system/file/delete.ts deleted file mode 100644 index ae6289019..000000000 --- a/projects/app/src/pages/api/system/file/delete.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { GridFSStorage } from '@/service/lib/gridfs'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - - const { fileId } = req.query as { fileId: string }; - - if (!fileId) { - throw new Error('fileId is empty'); - } - - const { userId } = await authUser({ req, authToken: true }); - - const gridFs = new GridFSStorage('dataset', userId); - - await gridFs.delete(fileId); - - jsonRes(res); - } catch (error) { - jsonRes(res, { - code: 500, - error - }); - } -} diff --git a/projects/app/src/pages/api/system/file/readUrl.ts b/projects/app/src/pages/api/system/file/readUrl.ts deleted file mode 100644 index 7661d7f36..000000000 --- a/projects/app/src/pages/api/system/file/readUrl.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import jwt from 'jsonwebtoken'; -import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode'; -import { GridFSStorage } from '@/service/lib/gridfs'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - - const { fileId } = req.query as { fileId: string }; - - if (!fileId) { - throw new Error('fileId is empty'); - } - - const { userId } = await authUser({ req, authToken: true }); - - // auth file - const gridFs = new GridFSStorage('dataset', userId); - await gridFs.findAndAuthFile(fileId); - - const token = await createFileToken({ - userId, - fileId - }); - - jsonRes(res, { - data: `/api/system/file/read?token=${token}` - }); - } catch (error) { - jsonRes(res, { - code: 500, - error - }); - } -} - -export const createFileToken = (data: { userId: string; fileId: string }) => { - if (!process.env.FILE_TOKEN_KEY) { - return Promise.reject('System unset FILE_TOKEN_KEY'); - } - const expiredTime = Math.floor(Date.now() / 1000) + 60 * 30; - - const key = process.env.FILE_TOKEN_KEY as string; - const token = jwt.sign( - { - ...data, - exp: expiredTime - }, - key - ); - return Promise.resolve(token); -}; - -export const authFileToken = (token?: string) => - new Promise<{ userId: string; fileId: string }>((resolve, reject) => { - if (!token) { - return reject(ERROR_ENUM.unAuthFile); - } - const key = process.env.FILE_TOKEN_KEY as string; - - jwt.verify(token, key, function (err, decoded: any) { - if (err || !decoded?.userId || !decoded?.fileId) { - reject(ERROR_ENUM.unAuthFile); - return; - } - resolve({ - userId: decoded.userId, - fileId: decoded.fileId - }); - }); - }); diff --git a/projects/app/src/pages/api/system/getInitData.ts b/projects/app/src/pages/api/system/getInitData.ts index 2be77f91a..5e054f115 100644 --- a/projects/app/src/pages/api/system/getInitData.ts +++ b/projects/app/src/pages/api/system/getInitData.ts @@ -1,9 +1,9 @@ import type { FeConfigsType, SystemEnvType } from '@fastgpt/global/common/system/types/index.d'; import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { readFileSync } from 'fs'; import type { InitDateResponse } from '@/global/common/api/systemRes'; -import { formatPrice } from '@fastgpt/global/common/bill/tools'; +import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; import { getTikTokenEnc } from '@/global/common/tiktoken'; import { initHttpAgent } from '@fastgpt/service/common/middle/httpAgent'; import { @@ -12,14 +12,16 @@ import { defaultCQModels, defaultExtractModels, defaultQGModels, - defaultVectorModels -} from '@/constants/model'; + defaultVectorModels, + defaultAudioSpeechModels +} from '@fastgpt/global/core/ai/model'; import { + AudioSpeechModelType, ChatModelItemType, FunctionModelItemType, LLMModelItemType, VectorModelItemType -} from '@/types/model'; +} from '@fastgpt/global/core/ai/model.d'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { getInitConfig(); @@ -66,8 +68,6 @@ export function initGlobal() { initHttpAgent(); global.qaQueueLen = 0; global.vectorQueueLen = 0; - global.sendInformQueue = []; - global.sendInformQueueLen = 0; } export function getInitConfig() { @@ -87,6 +87,7 @@ export function getInitConfig() { ExtractModels: FunctionModelItemType[]; QGModels: LLMModelItemType[]; VectorModels: VectorModelItemType[]; + AudioSpeechModels: AudioSpeechModelType[]; }; console.log(`System Version: ${global.systemVersion}`); @@ -105,6 +106,8 @@ export function getInitConfig() { global.qgModels = res.QGModels || defaultQGModels; global.vectorModels = res.VectorModels || defaultVectorModels; + + global.audioSpeechModels = res.AudioSpeechModels || defaultAudioSpeechModels; } catch (error) { setDefaultData(); console.log('get init config error, set default', error); @@ -122,19 +125,12 @@ export function setDefaultData() { global.qgModels = defaultQGModels; global.vectorModels = defaultVectorModels; + global.audioSpeechModels = defaultAudioSpeechModels; + global.priceMd = ''; console.log('use default config'); - console.log({ - feConfigs: defaultFeConfigs, - systemEnv: defaultSystemEnv, - chatModels: defaultChatModels, - qaModels: defaultQAModels, - cqModels: defaultCQModels, - extractModels: defaultExtractModels, - qgModels: defaultQGModels, - vectorModels: defaultVectorModels - }); + console.log(global); } export function getSystemVersion() { diff --git a/projects/app/src/pages/api/system/img/[id].ts b/projects/app/src/pages/api/system/img/[id].ts index 0b7ffedf5..e626466e4 100644 --- a/projects/app/src/pages/api/system/img/[id].ts +++ b/projects/app/src/pages/api/system/img/[id].ts @@ -1,5 +1,5 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { readMongoImg } from '@fastgpt/service/common/file/image/controller'; diff --git a/projects/app/src/pages/api/user/account/loginByPassword.ts b/projects/app/src/pages/api/user/account/loginByPassword.ts index 830d2204f..641b73957 100644 --- a/projects/app/src/pages/api/user/account/loginByPassword.ts +++ b/projects/app/src/pages/api/user/account/loginByPassword.ts @@ -1,25 +1,26 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { MongoUser } from '@fastgpt/service/support/user/schema'; -import { setCookie } from '@fastgpt/service/support/user/auth'; -import { generateToken } from '@fastgpt/service/support/user/auth'; +import { createJWT, setCookie } from '@fastgpt/service/support/permission/controller'; import { connectToDatabase } from '@/service/mongo'; +import { getUserDetail } from '@/service/support/user/controller'; +import type { PostLoginProps } from '@fastgpt/global/support/user/api.d'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { username, password } = req.body; + const { username, password, tmbId = '' } = req.body as PostLoginProps; if (!username || !password) { throw new Error('缺少参数'); } // 检测用户是否存在 - const authUser = await MongoUser.findOne({ + const authCert = await MongoUser.findOne({ username }); - if (!authUser) { + if (!authCert) { throw new Error('用户未注册'); } @@ -32,12 +33,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) throw new Error('密码错误'); } - const token = generateToken(user._id); + const userDetail = await getUserDetail({ tmbId, userId: user._id }); + + const token = createJWT(userDetail); setCookie(res, token); jsonRes(res, { data: { - user, + user: userDetail, token } }); diff --git a/projects/app/src/pages/api/user/account/loginout.ts b/projects/app/src/pages/api/user/account/loginout.ts index 29d770aa7..a3b8f79c1 100644 --- a/projects/app/src/pages/api/user/account/loginout.ts +++ b/projects/app/src/pages/api/user/account/loginout.ts @@ -1,7 +1,7 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { clearCookie } from '@fastgpt/service/support/user/auth'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { clearCookie } from '@fastgpt/service/support/permission/controller'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { diff --git a/projects/app/src/pages/api/user/account/tokenLogin.ts b/projects/app/src/pages/api/user/account/tokenLogin.ts index f1b9f89a9..164bcb1de 100644 --- a/projects/app/src/pages/api/user/account/tokenLogin.ts +++ b/projects/app/src/pages/api/user/account/tokenLogin.ts @@ -1,24 +1,17 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { MongoUser } from '@fastgpt/service/support/user/schema'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { connectToDatabase } from '@/service/mongo'; +import { getUserDetail } from '@/service/support/user/controller'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); - - // 根据 id 获取用户信息 - const user = await MongoUser.findById(userId); - - if (!user) { - throw new Error('账号异常'); - } + const { userId, tmbId } = await authCert({ req, authToken: true }); jsonRes(res, { - data: user + data: await getUserDetail({ tmbId, userId }) }); } catch (err) { jsonRes(res, { diff --git a/projects/app/src/pages/api/user/account/update.ts b/projects/app/src/pages/api/user/account/update.ts index c52f592e3..e685ca13a 100644 --- a/projects/app/src/pages/api/user/account/update.ts +++ b/projects/app/src/pages/api/user/account/update.ts @@ -1,8 +1,8 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; +import { jsonRes } from '@fastgpt/service/common/response'; import { MongoUser } from '@fastgpt/service/support/user/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { UserUpdateParams } from '@/types/user'; import { getAIApi, openaiBaseUrl } from '@fastgpt/service/core/ai/config'; import { connectToDatabase } from '@/service/mongo'; @@ -13,7 +13,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< await connectToDatabase(); const { avatar, timezone, openaiAccount } = req.body as UserUpdateParams; - const { userId } = await authUser({ req, authToken: true }); + const { userId } = await authCert({ req, authToken: true }); // auth key if (openaiAccount?.key) { diff --git a/projects/app/src/pages/api/user/account/updatePasswordByOld.ts b/projects/app/src/pages/api/user/account/updatePasswordByOld.ts index 75eda42d5..2cdd62665 100644 --- a/projects/app/src/pages/api/user/account/updatePasswordByOld.ts +++ b/projects/app/src/pages/api/user/account/updatePasswordByOld.ts @@ -1,7 +1,7 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { authUser } from '@fastgpt/service/support/user/auth'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { MongoUser } from '@fastgpt/service/support/user/schema'; import { connectToDatabase } from '@/service/mongo'; @@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< throw new Error('Params is missing'); } - const { userId } = await authUser({ req, authToken: true }); + const { userId } = await authCert({ req, authToken: true }); // auth old password const user = await MongoUser.findOne({ diff --git a/projects/app/src/pages/api/user/getBill.ts b/projects/app/src/pages/api/user/getBill.ts deleted file mode 100644 index 65f95d43c..000000000 --- a/projects/app/src/pages/api/user/getBill.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { Bill, connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { adaptBill } from '@/utils/adapt'; -import { addDays } from 'date-fns'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { - pageNum = 1, - pageSize = 10, - dateStart = addDays(new Date(), -7), - dateEnd = new Date() - } = req.body as { - pageNum: number; - pageSize: number; - dateStart: Date; - dateEnd: Date; - }; - - const { userId } = await authUser({ req, authToken: true }); - - const where = { - userId, - time: { - $gte: dateStart, - $lte: dateEnd - } - }; - - // get bill record and total by record - const [bills, total] = await Promise.all([ - Bill.find(where) - .sort({ time: -1 }) // 按照创建时间倒序排列 - .skip((pageNum - 1) * pageSize) - .limit(pageSize), - Bill.countDocuments(where) - ]); - - jsonRes(res, { - data: { - pageNum, - pageSize, - data: bills.map(adaptBill), - total - } - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/user/getPayOrders.ts b/projects/app/src/pages/api/user/getPayOrders.ts deleted file mode 100644 index 6e068c981..000000000 --- a/projects/app/src/pages/api/user/getPayOrders.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { connectToDatabase } from '@/service/mongo'; -import { MongoPay } from '@fastgpt/service/support/wallet/pay/schema'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); - - const records = await MongoPay.find({ - userId, - status: { $ne: 'CLOSED' } - }) - .sort({ createTime: -1 }) - .limit(100); - - jsonRes(res, { - data: records - }); - } catch (err) { - console.log(err); - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/user/inform/countUnread.ts b/projects/app/src/pages/api/user/inform/countUnread.ts deleted file mode 100644 index b6f46d9e8..000000000 --- a/projects/app/src/pages/api/user/inform/countUnread.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase } from '@/service/mongo'; -import { MongoUserInform } from '@fastgpt/service/support/user/inform/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - if (!req.headers.cookie) { - return jsonRes(res, { - data: 0 - }); - } - const { userId } = await authUser({ req, authToken: true }); - - const data = await MongoUserInform.countDocuments({ - userId, - read: false - }); - - jsonRes(res, { - data - }); - } catch (err) { - jsonRes(res, { - data: 0 - }); - } -} diff --git a/projects/app/src/pages/api/user/inform/list.ts b/projects/app/src/pages/api/user/inform/list.ts deleted file mode 100644 index 82b6646a3..000000000 --- a/projects/app/src/pages/api/user/inform/list.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { MongoUserInform } from '@fastgpt/service/support/user/inform/schema'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); - - const { pageNum, pageSize = 10 } = req.body as { - pageNum: number; - pageSize: number; - }; - - const [informs, total] = await Promise.all([ - MongoUserInform.find({ userId }) - .sort({ time: -1 }) // 按照创建时间倒序排列 - .skip((pageNum - 1) * pageSize) - .limit(pageSize), - MongoUserInform.countDocuments({ userId }) - ]); - - jsonRes(res, { - data: { - pageNum, - pageSize, - data: informs, - total - } - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/user/inform/read.ts b/projects/app/src/pages/api/user/inform/read.ts deleted file mode 100644 index 3863efa41..000000000 --- a/projects/app/src/pages/api/user/inform/read.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { MongoUserInform } from '@fastgpt/service/support/user/inform/schema'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); - - const { id } = req.query as { id: string }; - - await MongoUserInform.findOneAndUpdate( - { - _id: id, - userId - }, - { - read: true - } - ); - - jsonRes(res); - } catch (err) { - jsonRes(res); - } -} diff --git a/projects/app/src/pages/api/user/inform/send.ts b/projects/app/src/pages/api/user/inform/send.ts deleted file mode 100644 index 01451a091..000000000 --- a/projects/app/src/pages/api/user/inform/send.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { startSendInform } from '@/service/events/sendInform'; -import { MongoUserInform } from '@fastgpt/service/support/user/inform/schema'; -import { InformTypeEnum } from '@fastgpt/global/support/user/constant'; -import { - sendInform2AllUser, - sendInform2OneUser -} from '@fastgpt/service/support/user/inform/controller'; - -export type Props = { - type: `${InformTypeEnum}`; - title: string; - content: string; - userId?: string; -}; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - await authUser({ req, authRoot: true }); - - jsonRes(res, { - data: await sendInform(req.body), - message: '发送通知成功' - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} - -export async function sendInform({ type, title, content, userId }: Props) { - if (!type || !title || !content) { - return; - } - - try { - if (userId) { - global.sendInformQueue.push(async () => sendInform2OneUser({ type, title, content, userId })); - startSendInform(); - return; - } - - // send to all user - sendInform2AllUser({ type, title, content }); - } catch (error) { - console.log('send inform error', error); - } -} diff --git a/projects/app/src/pages/api/user/promotion/getPromotionData.ts b/projects/app/src/pages/api/user/promotion/getPromotionData.ts deleted file mode 100644 index 76e457c71..000000000 --- a/projects/app/src/pages/api/user/promotion/getPromotionData.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase } from '@/service/mongo'; -import { MongoPromotionRecord } from '@fastgpt/service/support/activity/promotion/schema'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import mongoose from '@fastgpt/service/common/mongo'; -import { MongoUser } from '@fastgpt/service/support/user/schema'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); - - const invitedAmount = await MongoUser.countDocuments({ - inviterId: userId - }); - - // 计算累计合 - const countHistory: { totalAmount: number }[] = await MongoPromotionRecord.aggregate([ - { - $match: { - userId: new mongoose.Types.ObjectId(userId), - amount: { $gt: 0 } - } - }, - { - $group: { - _id: null, // 分组条件,这里使用 null 表示不分组 - totalAmount: { $sum: '$amount' } // 计算 amount 字段的总和 - } - }, - { - $project: { - _id: false, // 排除 _id 字段 - totalAmount: true // 只返回 totalAmount 字段 - } - } - ]); - - jsonRes(res, { - data: { - invitedAmount, - earningsAmount: countHistory[0]?.totalAmount || 0 - } - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/user/promotion/getPromotions.ts b/projects/app/src/pages/api/user/promotion/getPromotions.ts deleted file mode 100644 index 021aaa97e..000000000 --- a/projects/app/src/pages/api/user/promotion/getPromotions.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase } from '@/service/mongo'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { MongoPromotionRecord } from '@fastgpt/service/support/activity/promotion/schema'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - let { pageNum = 1, pageSize = 10 } = req.body as { - pageNum: number; - pageSize: number; - }; - - const { userId } = await authUser({ req, authToken: true }); - - const data = await MongoPromotionRecord.find( - { - userId - }, - '_id createTime type amount' - ) - .sort({ _id: -1 }) - .skip((pageNum - 1) * pageSize) - .limit(pageSize); - - jsonRes(res, { - data: { - pageNum, - pageSize, - data, - total: await MongoPromotionRecord.countDocuments({ - userId - }) - } - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/v1/chat/completions.ts b/projects/app/src/pages/api/v1/chat/completions.ts index 8cf686594..dc0effb2a 100644 --- a/projects/app/src/pages/api/v1/chat/completions.ts +++ b/projects/app/src/pages/api/v1/chat/completions.ts @@ -1,49 +1,30 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { authApp } from '@/service/utils/auth'; -import { authUser, AuthUserTypeEnum } from '@fastgpt/service/support/user/auth'; -import { sseErrRes, jsonRes } from '@/service/response'; -import { addLog } from '@/service/utils/tools'; +import { authApp } from '@fastgpt/service/support/permission/auth/app'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { sseErrRes, jsonRes } from '@fastgpt/service/common/response'; +import { addLog } from '@fastgpt/service/common/mongo/controller'; import { withNextCors } from '@fastgpt/service/common/middle/cors'; -import { ChatRoleEnum, ChatSourceEnum, sseResponseEventEnum } from '@/constants/chat'; -import { - dispatchHistory, - dispatchChatInput, - dispatchChatCompletion, - dispatchDatasetSearch, - dispatchAnswer, - dispatchClassifyQuestion, - dispatchContentExtract, - dispatchHttpRequest, - dispatchAppRequest, - dispatchRunPlugin, - dispatchPluginInput, - dispatchPluginOutput -} from '@/service/moduleDispatch'; -import type { CreateChatCompletionRequest } from '@fastgpt/global/core/ai/type.d'; -import type { MessageItemType } from '@/types/core/chat/type'; +import { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; +import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant'; +import { dispatchModules } from '@/service/moduleDispatch'; +import type { ChatCompletionCreateParams } from '@fastgpt/global/core/ai/type.d'; +import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d'; import { gptMessage2ChatType, textAdaptGptResponse } from '@/utils/adapt'; import { getChatHistory } from './getHistory'; import { saveChat } from '@/service/utils/chat/saveChat'; import { responseWrite } from '@fastgpt/service/common/response'; -import { TaskResponseKeyEnum } from '@/constants/chat'; -import { initModuleType } from '@/constants/flow'; -import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant'; -import { RunningModuleItemType } from '@/types/app'; -import type { ModuleItemType } from '@fastgpt/global/core/module/type'; -import { pushChatBill } from '@/service/common/bill/push'; -import { BillSourceEnum } from '@/constants/user'; -import { ChatHistoryItemResType } from '@/types/chat'; -import type { UserModelSchema } from '@fastgpt/global/support/user/type'; -import { SystemInputEnum, SystemOutputEnum } from '@/constants/app'; -import { getSystemTime } from '@fastgpt/global/common/time/timezone'; -import { authOutLinkChat } from '@fastgpt/service/support/outLink/auth'; +import { pushChatBill } from '@/service/support/wallet/bill/push'; +import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants'; +import { authOutLinkChat } from '@/service/support/permission/auth/outLink'; import { pushResult2Remote, updateOutLinkUsage } from '@fastgpt/service/support/outLink/tools'; import requestIp from 'request-ip'; -import { replaceVariable } from '@/global/common/string/tools'; -import type { ModuleDispatchProps } from '@/types/core/chat/type'; + import { selectShareResponse } from '@/utils/service/core/chat'; import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools'; import { connectToDatabase } from '@/service/mongo'; +import { getUserAndAuthBalance } from '@/service/support/permission/auth/user'; +import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { MongoApp } from '@fastgpt/service/core/app/schema'; type FastGptWebChatProps = { chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history @@ -53,10 +34,10 @@ type FastGptShareChatProps = { shareId?: string; authToken?: string; }; -export type Props = CreateChatCompletionRequest & +export type Props = ChatCompletionCreateParams & FastGptWebChatProps & FastGptShareChatProps & { - messages: MessageItemType[]; + messages: ChatMessageItemType[]; stream?: boolean; detail?: boolean; variables: Record; @@ -101,68 +82,98 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex let startTime = Date.now(); - /* user auth */ - const { - responseDetail: shareResponseDetail, - user, - userId, - appId: authAppid, - authType, - apikey - } = await (async (): Promise<{ - user?: UserModelSchema; - responseDetail?: boolean; - userId: string; - appId: string; - authType: `${AuthUserTypeEnum}`; - apikey?: string; - }> => { + const chatMessages = gptMessage2ChatType(messages); + if (chatMessages[chatMessages.length - 1].obj !== ChatRoleEnum.Human) { + chatMessages.pop(); + } + // user question + const question = chatMessages.pop(); + if (!question) { + throw new Error('Question is empty'); + } + + /* auth app permission */ + const { user, app, responseDetail, authType, apikey, canWrite } = await (async () => { if (shareId) { - return authOutLinkChat({ + const { user, app, authType, responseDetail } = await authOutLinkChat({ shareId, ip: requestIp.getClientIp(req), authToken, - question: - (messages[messages.length - 2]?.role === 'user' - ? messages[messages.length - 2].content - : messages[messages.length - 1]?.content) || '' + question: question.value }); + return { + user, + app, + responseDetail, + apikey: '', + authType, + canWrite: false + }; } - return authUser({ req, authToken: true, authApiKey: true, authBalance: true }); + + const { + appId: apiKeyAppId, + tmbId, + authType, + apikey + } = await authCert({ + req, + authToken: true, + authApiKey: true + }); + + const user = await getUserAndAuthBalance({ + tmbId, + minBalance: 0 + }); + + // openapi key + if (authType === AuthUserTypeEnum.apikey) { + const app = await MongoApp.findById(apiKeyAppId); + + if (!app) { + return Promise.reject('app is empty'); + } + + return { + user, + app, + responseDetail: detail, + apikey, + authType, + canWrite: false + }; + } + + if (!appId) { + return Promise.reject('appId is empty'); + } + + // token + const { app, canWrite } = await authApp({ + req, + authToken: true, + appId, + per: 'r' + }); + + return { + user, + app, + responseDetail: detail, + apikey, + authType, + canWrite: canWrite || false + }; })(); - if (!user) { - throw new Error('Account is error'); - } - - // must have a app - appId = appId ? appId : authAppid; - if (!appId) { - throw new Error('appId is empty'); - } - // auth app, get history - const [{ app }, { history }] = await Promise.all([ - authApp({ - appId, - userId - }), - getChatHistory({ chatId, appId, userId }) - ]); + const { history } = await getChatHistory({ chatId, tmbId: user.team.tmbId }); - const isOwner = !shareId && userId === String(app.userId); - const responseDetail = isOwner || shareResponseDetail; + const isOwner = !shareId && String(user.team.tmbId) === String(app.tmbId); /* format prompts */ const prompts = history.concat(gptMessage2ChatType(messages)); - if (prompts[prompts.length - 1]?.obj === 'AI') { - prompts.pop(); - } - // user question - const prompt = prompts.pop(); - if (!prompt) { - throw new Error('Question is empty'); - } // set sse response headers if (stream) { @@ -175,12 +186,15 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex /* start flow controller */ const { responseData, answerText } = await dispatchModules({ res, + chatId, modules: app.modules, user, + teamId: user.team.teamId, + tmbId: user.team.tmbId, variables, params: { history: prompts, - userChatInput: prompt.value + userChatInput: question.value }, stream, detail @@ -190,8 +204,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex if (chatId) { await saveChat({ chatId, - appId, - userId, + appId: app._id, + teamId: user.team.teamId, + tmbId: user.team.tmbId, variables, isOwner, // owner update use time shareId, @@ -205,7 +220,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex return ChatSourceEnum.online; })(), content: [ - prompt, + question, { dataId: messages[messages.length - 1].dataId, obj: ChatRoleEnum.AI, @@ -219,7 +234,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex addLog.info(`completions running time: ${(Date.now() - startTime) / 1000}s`); /* select fe response field */ - const feResponseData = isOwner ? responseData : selectShareResponse({ responseData }); + const feResponseData = canWrite ? responseData : selectShareResponse({ responseData }); if (stream) { responseWrite({ @@ -264,8 +279,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex // add record const { total } = pushChatBill({ appName: app.name, - appId, - userId, + appId: app._id, + teamId: user.team.teamId, + tmbId: user.team.tmbId, source: (() => { if (authType === 'apikey') return BillSourceEnum.api; if (shareId) return BillSourceEnum.shareLink; @@ -281,11 +297,12 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex total }); } - !!apikey && + if (apikey) { updateApiKeyUsage({ apikey, usage: total }); + } } catch (err: any) { if (stream) { sseErrRes(res, err); @@ -299,266 +316,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex } }); -/* running */ -export async function dispatchModules({ - res, - modules, - user, - params = {}, - variables = {}, - stream = false, - detail = false -}: { - res: NextApiResponse; - modules: ModuleItemType[]; - user: UserModelSchema; - params?: Record; - variables?: Record; - stream?: boolean; - detail?: boolean; -}) { - variables = { - ...getSystemVariable({ timezone: user.timezone }), - ...variables - }; - const runningModules = loadModules(modules, variables); - - // let storeData: Record = {}; // after module used - let chatResponse: ChatHistoryItemResType[] = []; // response request and save to database - let chatAnswerText = ''; // AI answer - let runningTime = Date.now(); - - function pushStore( - { inputs = [] }: RunningModuleItemType, - { - answerText = '', - responseData - }: { - answerText?: string; - responseData?: ChatHistoryItemResType | ChatHistoryItemResType[]; - } - ) { - const time = Date.now(); - if (responseData) { - if (Array.isArray(responseData)) { - chatResponse = chatResponse.concat(responseData); - } else { - chatResponse.push({ - ...responseData, - runningTime: +((time - runningTime) / 1000).toFixed(2) - }); - } - } - runningTime = time; - - const isResponseAnswerText = - inputs.find((item) => item.key === SystemInputEnum.isResponseAnswerText)?.value ?? true; - if (isResponseAnswerText) { - chatAnswerText += answerText; - } - } - function moduleInput( - module: RunningModuleItemType, - data: Record = {} - ): Promise { - const checkInputFinish = () => { - return !module.inputs.find((item: any) => item.value === undefined); - }; - const updateInputValue = (key: string, value: any) => { - const index = module.inputs.findIndex((item: any) => item.key === key); - if (index === -1) return; - module.inputs[index].value = value; - }; - - const set = new Set(); - - return Promise.all( - Object.entries(data).map(([key, val]: any) => { - updateInputValue(key, val); - - if (!set.has(module.moduleId) && checkInputFinish()) { - set.add(module.moduleId); - // remove switch - updateInputValue(SystemInputEnum.switch, undefined); - return moduleRun(module); - } - }) - ); - } - function moduleOutput( - module: RunningModuleItemType, - result: Record = {} - ): Promise { - pushStore(module, result); - return Promise.all( - module.outputs.map((outputItem) => { - if (result[outputItem.key] === undefined) return; - /* update output value */ - outputItem.value = result[outputItem.key]; - - /* update target */ - return Promise.all( - outputItem.targets.map((target: any) => { - // find module - const targetModule = runningModules.find((item) => item.moduleId === target.moduleId); - if (!targetModule) return; - - return moduleInput(targetModule, { [target.key]: outputItem.value }); - }) - ); - }) - ); - } - async function moduleRun(module: RunningModuleItemType): Promise { - if (res.closed) return Promise.resolve(); - - if (stream && detail && module.showStatus) { - responseStatus({ - res, - name: module.name, - status: 'running' - }); - } - - // get fetch params - const params: Record = {}; - module.inputs.forEach((item: any) => { - params[item.key] = item.value; - }); - const props: ModuleDispatchProps> = { - res, - stream, - detail, - variables, - outputs: module.outputs, - user, - inputs: params - }; - - const dispatchRes: Record = await (async () => { - const callbackMap: Record = { - [FlowNodeTypeEnum.historyNode]: dispatchHistory, - [FlowNodeTypeEnum.questionInput]: dispatchChatInput, - [FlowNodeTypeEnum.answerNode]: dispatchAnswer, - [FlowNodeTypeEnum.chatNode]: dispatchChatCompletion, - [FlowNodeTypeEnum.datasetSearchNode]: dispatchDatasetSearch, - [FlowNodeTypeEnum.classifyQuestion]: dispatchClassifyQuestion, - [FlowNodeTypeEnum.contentExtract]: dispatchContentExtract, - [FlowNodeTypeEnum.httpRequest]: dispatchHttpRequest, - [FlowNodeTypeEnum.runApp]: dispatchAppRequest, - [FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin, - [FlowNodeTypeEnum.pluginInput]: dispatchPluginInput, - [FlowNodeTypeEnum.pluginOutput]: dispatchPluginOutput - }; - if (callbackMap[module.flowType]) { - return callbackMap[module.flowType](props); - } - return {}; - })(); - - const formatResponseData = (() => { - if (!dispatchRes[TaskResponseKeyEnum.responseData]) return undefined; - if (Array.isArray(dispatchRes[TaskResponseKeyEnum.responseData])) - return dispatchRes[TaskResponseKeyEnum.responseData]; - return { - ...dispatchRes[TaskResponseKeyEnum.responseData], - moduleName: module.name, - moduleType: module.flowType - }; - })(); - - return moduleOutput(module, { - [SystemOutputEnum.finish]: true, - ...dispatchRes, - [TaskResponseKeyEnum.responseData]: formatResponseData - }); - } - - // start process width initInput - const initModules = runningModules.filter((item) => initModuleType[item.flowType]); - - await Promise.all(initModules.map((module) => moduleInput(module, params))); - - return { - [TaskResponseKeyEnum.answerText]: chatAnswerText, - [TaskResponseKeyEnum.responseData]: chatResponse - }; -} - -/* init store modules to running modules */ -function loadModules( - modules: ModuleItemType[], - variables: Record -): RunningModuleItemType[] { - return modules.map((module) => { - return { - moduleId: module.moduleId, - name: module.name, - flowType: module.flowType, - showStatus: module.showStatus, - inputs: module.inputs - .filter((item) => item.connected) // filter unconnected target input - .map((item) => { - if (typeof item.value !== 'string') { - return { - key: item.key, - value: item.value - }; - } - - // variables replace - const replacedVal = replaceVariable(item.value, variables); - - return { - key: item.key, - value: replacedVal - }; - }), - outputs: module.outputs - .map((item) => ({ - key: item.key, - answer: item.key === TaskResponseKeyEnum.answerText, - value: undefined, - targets: item.targets - })) - .sort((a, b) => { - // finish output always at last - if (a.key === SystemOutputEnum.finish) return 1; - if (b.key === SystemOutputEnum.finish) return -1; - return 0; - }) - }; - }); -} - -/* sse response modules staus */ -export function responseStatus({ - res, - status, - name -}: { - res: NextApiResponse; - status?: 'running' | 'finish'; - name?: string; -}) { - if (!name) return; - responseWrite({ - res, - event: sseResponseEventEnum.moduleStatus, - data: JSON.stringify({ - status: 'running', - name - }) - }); -} - -/* get system variable */ -export function getSystemVariable({ timezone }: { timezone: string }) { - return { - cTime: getSystemTime(timezone) - }; -} - export const config = { api: { responseLimit: '20mb' diff --git a/projects/app/src/pages/api/v1/chat/getHistory.ts b/projects/app/src/pages/api/v1/chat/getHistory.ts index 157008dce..86fbd34a3 100644 --- a/projects/app/src/pages/api/v1/chat/getHistory.ts +++ b/projects/app/src/pages/api/v1/chat/getHistory.ts @@ -1,10 +1,11 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { authUser } from '@fastgpt/service/support/user/auth'; -import { ChatItem, connectToDatabase } from '@/service/mongo'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { connectToDatabase } from '@/service/mongo'; +import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; import { Types } from '@fastgpt/service/common/mongo'; -import type { ChatItemType } from '@/types/chat'; +import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; export type Props = { appId?: string; @@ -16,13 +17,13 @@ export type Response = { history: ChatItemType[] }; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { userId } = await authUser({ req, authToken: true }); + const { tmbId } = await authCert({ req, authToken: true }); const { chatId, limit } = req.body as Props; jsonRes(res, { data: await getChatHistory({ chatId, - userId, + tmbId, limit }) }); @@ -36,20 +37,18 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) export async function getChatHistory({ chatId, - userId, - appId, + tmbId, limit = 30 -}: Props & { userId: string }): Promise { - if (!chatId || !appId) { +}: Props & { tmbId: string }): Promise { + if (!chatId) { return { history: [] }; } - const history = await ChatItem.aggregate([ + const history = await MongoChatItem.aggregate([ { $match: { chatId, - appId: new Types.ObjectId(appId), - userId: new Types.ObjectId(userId) + tmbId: new Types.ObjectId(tmbId) } }, { diff --git a/projects/app/src/pages/api/v1/embeddings.ts b/projects/app/src/pages/api/v1/embeddings.ts new file mode 100644 index 000000000..157a8d91c --- /dev/null +++ b/projects/app/src/pages/api/v1/embeddings.ts @@ -0,0 +1,58 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@fastgpt/service/common/response'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { withNextCors } from '@fastgpt/service/common/middle/cors'; +import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push'; +import { connectToDatabase } from '@/service/mongo'; +import { authTeamBalance } from '@/service/support/permission/auth/bill'; +import { getVectorsByText, GetVectorProps } from '@/service/core/ai/vector'; + +type Props = GetVectorProps & { + billId?: string; +}; + +export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + let { input, model, billId } = req.body as Props; + await connectToDatabase(); + const { teamId, tmbId } = await authCert({ req, authToken: true }); + + if (!Array.isArray(input) || typeof input !== 'string') { + throw new Error('input is nor array or string'); + } + + await authTeamBalance(teamId); + + const { tokenLen, vectors } = await getVectorsByText({ input, model }); + + pushGenerateVectorBill({ + teamId, + tmbId, + tokenLen: tokenLen, + model, + billId + }); + + jsonRes(res, { + data: { + object: 'list', + data: vectors.map((item, index) => ({ + object: 'embedding', + index: index, + embedding: item + })), + model, + usage: { + prompt_tokens: tokenLen, + total_tokens: tokenLen + } + } + }); + } catch (err) { + console.log(err); + jsonRes(res, { + code: 500, + error: err + }); + } +}); diff --git a/projects/app/src/pages/app/detail/components/AdEdit/Header.tsx b/projects/app/src/pages/app/detail/components/AdEdit/Header.tsx index 8123a6eac..ab1a3885e 100644 --- a/projects/app/src/pages/app/detail/components/AdEdit/Header.tsx +++ b/projects/app/src/pages/app/detail/components/AdEdit/Header.tsx @@ -5,17 +5,18 @@ import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant import { FlowNodeOutputTargetItemType } from '@fastgpt/global/core/module/node/type'; import { ModuleItemType } from '@fastgpt/global/core/module/type'; import { useRequest } from '@/web/common/hooks/useRequest'; -import type { AppSchema } from '@/types/mongoSchema'; +import { AppSchema } from '@fastgpt/global/core/app/type.d'; import { useUserStore } from '@/web/support/user/useUserStore'; import { useTranslation } from 'next-i18next'; import { useCopyData } from '@/web/common/hooks/useCopyData'; -import { AppTypeEnum } from '@/constants/app'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import dynamic from 'next/dynamic'; import MyIcon from '@/components/Icon'; import MyTooltip from '@/components/MyTooltip'; import ChatTest, { type ChatTestComponentRef } from '@/components/core/module/Flow/ChatTest'; import { flowNode2Modules, useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider'; +import { useAppStore } from '@/web/core/app/store/useAppStore'; const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings')); @@ -36,7 +37,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({ const { t } = useTranslation(); const { copyData } = useCopyData(); const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure(); - const { updateAppDetail } = useUserStore(); + const { updateAppDetail } = useAppStore(); const { nodes, edges, onFixView } = useFlowProviderStore(); @@ -56,7 +57,8 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({ return updateAppDetail(app._id, { modules, - type: AppTypeEnum.advanced + type: AppTypeEnum.advanced, + permission: undefined }); }, successToast: '保存配置成功', diff --git a/projects/app/src/pages/app/detail/components/AdEdit/index.tsx b/projects/app/src/pages/app/detail/components/AdEdit/index.tsx index c211a4b9a..5dd56cde9 100644 --- a/projects/app/src/pages/app/detail/components/AdEdit/index.tsx +++ b/projects/app/src/pages/app/detail/components/AdEdit/index.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import { AppSchema } from '@/types/mongoSchema'; +import { AppSchema } from '@fastgpt/global/core/app/type.d'; import Header from './Header'; import Flow from '@/components/core/module/Flow'; import FlowProvider, { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider'; diff --git a/projects/app/src/pages/app/detail/components/BasicEdit/index.tsx b/projects/app/src/pages/app/detail/components/BasicEdit/index.tsx index 14741593d..4f7bebf0b 100644 --- a/projects/app/src/pages/app/detail/components/BasicEdit/index.tsx +++ b/projects/app/src/pages/app/detail/components/BasicEdit/index.tsx @@ -15,9 +15,7 @@ import { TableContainer, useDisclosure, Button, - IconButton, - Text, - Switch + IconButton } from '@chakra-ui/react'; import { useUserStore } from '@/web/support/user/useUserStore'; import { useQuery } from '@tanstack/react-query'; @@ -31,12 +29,8 @@ import { type EditFormType } from '@/web/core/app/basicSettings'; import { chatModelList } from '@/web/common/system/staticData'; -import { formatPrice } from '@fastgpt/global/common/bill/tools'; -import { - ChatModelSystemTip, - welcomeTextTip, - questionGuideTip -} from '@/constants/flow/ModuleTemplate'; +import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; +import { ChatModelSystemTip, welcomeTextTip } from '@/constants/flow/ModuleTemplate'; import { VariableItemType } from '@/types/app'; import type { ModuleItemType } from '@fastgpt/global/core/module/type'; import { useRequest } from '@/web/common/hooks/useRequest'; @@ -45,7 +39,7 @@ import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant'; import { streamFetch } from '@/web/common/api/fetch'; import { useRouter } from 'next/router'; import { useToast } from '@/web/common/hooks/useToast'; -import { AppSchema } from '@/types/mongoSchema'; +import { AppSchema } from '@fastgpt/global/core/app/type.d'; import { delModelById } from '@/web/core/app/api'; import { useTranslation } from 'react-i18next'; import { getGuideModule } from '@/global/core/app/modules/utils'; @@ -59,8 +53,12 @@ import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ import { addVariable } from '@/components/core/module/VariableEditModal'; import { KbParamsModal } from '@/components/core/module/DatasetSelectModal'; -import { AppTypeEnum } from '@/constants/app'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { useDatasetStore } from '@/web/core/dataset/store/dataset'; +import { useAppStore } from '@/web/core/app/store/useAppStore'; +import PermissionIconText from '@/components/support/permission/IconText'; +import QGSwitch from '../QGSwitch'; +import TTSSelect from '../TTSSelect'; const VariableEditModal = dynamic(() => import('@/components/core/module/VariableEditModal')); const InfoModal = dynamic(() => import('../InfoModal')); @@ -72,7 +70,7 @@ const Settings = ({ appId }: { appId: string }) => { const router = useRouter(); const { t } = useTranslation(); const { toast } = useToast(); - const { appDetail, updateAppDetail } = useUserStore(); + const { appDetail, updateAppDetail } = useAppStore(); const { loadAllDatasets, allDatasets } = useDatasetStore(); const { isPc } = useSystemStore(); @@ -103,7 +101,7 @@ const Settings = ({ appId }: { appId: string }) => { }); const { fields: datasets, replace: replaceKbList } = useFieldArray({ control, - name: 'kb.list' + name: 'dataset.list' }); const { @@ -130,7 +128,7 @@ const Settings = ({ appId }: { appId: string }) => { }, [refresh]); const selectDatasets = useMemo( - () => allDatasets.filter((item) => datasets.find((kb) => kb.datasetId === item._id)), + () => allDatasets.filter((item) => datasets.find((dataset) => dataset.datasetId === item._id)), [allDatasets, datasets] ); @@ -144,12 +142,12 @@ const Settings = ({ appId }: { appId: string }) => { onSuccess(res) { if (!res) return; toast({ - title: '删除成功', + title: t('common.Delete Success'), status: 'success' }); router.replace(`/app/list`); }, - errorToast: '删除失败' + errorToast: t('common.Delete Failed') }); const appModule2Form = useCallback(() => { @@ -166,11 +164,12 @@ const Settings = ({ appId }: { appId: string }) => { await updateAppDetail(appDetail._id, { modules, - type: AppTypeEnum.basic + type: AppTypeEnum.basic, + permission: undefined }); }, - successToast: '保存成功', - errorToast: '保存出现异常' + successToast: t('common.Save Success'), + errorToast: t('common.Save Failed') }); useEffect(() => { @@ -208,14 +207,15 @@ const Settings = ({ appId }: { appId: string }) => { borderColor={'myGray.200'} p={4} pt={[0, 4]} + pb={10} overflow={'overlay'} > - {t('app.Basic Settings')} + - ( + (AppId:{' '} {appId} @@ -237,23 +237,25 @@ const Settings = ({ appId }: { appId: string }) => { {appDetail.name} - } - variant={'base'} - borderRadius={'md'} - aria-label={'delete'} - _hover={{ - bg: 'myGray.100', - color: 'red.600' - }} - isLoading={isLoading} - onClick={openConfirmDel(handleDelModel)} - /> + {appDetail.isOwner && ( + } + variant={'base'} + borderRadius={'md'} + aria-label={'delete'} + _hover={{ + bg: 'myGray.100', + color: 'red.600' + }} + isLoading={isLoading} + onClick={openConfirmDel(handleDelModel)} + /> + )} { > 外接 - + {appDetail.isOwner && ( + + )} @@ -424,7 +428,7 @@ const Settings = ({ appId }: { appId: string }) => { setValue('chatModel.model', val); const maxToken = chatModelList.find((item) => item.model === getValues('chatModel.model')) - ?.maxToken || 4000; + ?.maxResponse || 4000; const token = maxToken / 2; setValue('chatModel.maxToken', token); setRefresh(!refresh); @@ -448,7 +452,7 @@ const Settings = ({ appId }: { appId: string }) => { - {/* kb */} + {/* dataset */} @@ -465,9 +469,11 @@ const Settings = ({ appId }: { appId: string }) => { - {t('core.dataset.Similarity')}: {getValues('kb.searchSimilarity')},{' '} - {t('core.dataset.Search Top K')}: {getValues('kb.searchLimit')} - {getValues('kb.searchEmptyText') === '' ? '' : t('core.dataset.Set Empty Result Tip')} + {t('core.dataset.Similarity')}: {getValues('dataset.searchSimilarity')},{' '} + {t('core.dataset.Search Top K')}: {getValues('dataset.searchLimit')} + {getValues('dataset.searchEmptyText') === '' + ? '' + : t('core.dataset.Set Empty Result Tip')} { - - - {t('core.app.Next Step Guide')} - - - - - { - const value = e.target.checked; - setValue('questionGuide', value); - setRefresh((state) => !state); - }} - /> - + { + setValue('tts', e); + setRefresh((state) => !state); + }} + /> + + + + { + const value = e.target.checked; + setValue('questionGuide', value); + setRefresh((state) => !state); + }} + /> @@ -577,14 +585,14 @@ const Settings = ({ appId }: { appId: string }) => { {isOpenKbParams && ( { - setValue('kb.searchEmptyText', searchEmptyText); - setValue('kb.searchLimit', searchLimit); - setValue('kb.searchSimilarity', searchSimilarity); + setValue('dataset.searchEmptyText', searchEmptyText); + setValue('dataset.searchLimit', searchLimit); + setValue('dataset.searchSimilarity', searchSimilarity); setRefresh((state) => !state); }} /> @@ -595,7 +603,8 @@ const Settings = ({ appId }: { appId: string }) => { const ChatTest = ({ appId }: { appId: string }) => { const { t } = useTranslation(); - const { appDetail, userInfo } = useUserStore(); + const { userInfo } = useUserStore(); + const { appDetail } = useAppStore(); const ChatBoxRef = useRef(null); const [modules, setModules] = useState([]); @@ -609,7 +618,7 @@ const ChatTest = ({ appId }: { appId: string }) => { // 流请求,获取数据 const { responseText, responseData } = await streamFetch({ - url: '/api/chat/chatTest', + url: '/api/core/chat/chatTest', data: { history, prompt: chatList[chatList.length - 2].value, diff --git a/projects/app/src/pages/app/detail/components/Charts/TotalUsage.tsx b/projects/app/src/pages/app/detail/components/Charts/TotalUsage.tsx index 0d11b762b..13650ccb5 100644 --- a/projects/app/src/pages/app/detail/components/Charts/TotalUsage.tsx +++ b/projects/app/src/pages/app/detail/components/Charts/TotalUsage.tsx @@ -4,7 +4,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore'; import { getAppTotalUsage } from '@/web/core/app/api'; import { useQuery } from '@tanstack/react-query'; import dayjs from 'dayjs'; -import { formatPrice } from '@fastgpt/global/common/bill/tools'; +import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; import Loading from '@/components/Loading'; import { Box } from '@chakra-ui/react'; diff --git a/projects/app/src/pages/app/detail/components/InfoModal.tsx b/projects/app/src/pages/app/detail/components/InfoModal.tsx index 69140c4b5..f24ff5647 100644 --- a/projects/app/src/pages/app/detail/components/InfoModal.tsx +++ b/projects/app/src/pages/app/detail/components/InfoModal.tsx @@ -10,15 +10,17 @@ import { ModalBody } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; -import { AppSchema } from '@/types/mongoSchema'; +import { AppSchema } from '@fastgpt/global/core/app/type.d'; import { useToast } from '@/web/common/hooks/useToast'; import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; -import { compressImg } from '@/web/common/file/utils'; +import { compressImgAndUpload } from '@/web/common/file/controller'; import { getErrText } from '@fastgpt/global/common/error/utils'; -import { useUserStore } from '@/web/support/user/useUserStore'; import { useRequest } from '@/web/common/hooks/useRequest'; import Avatar from '@/components/Avatar'; import MyModal from '@/components/MyModal'; +import { useAppStore } from '@/web/core/app/store/useAppStore'; +import PermissionRadio from '@/components/support/permission/Radio'; +import { useTranslation } from 'react-i18next'; const InfoModal = ({ defaultApp, @@ -29,8 +31,9 @@ const InfoModal = ({ onClose: () => void; onSuccess?: () => void; }) => { + const { t } = useTranslation(); const { toast } = useToast(); - const { updateAppDetail } = useUserStore(); + const { updateAppDetail } = useAppStore(); const { File, onOpen: onOpenSelectFile } = useSelectFile({ fileType: '.jpg,.png', @@ -55,7 +58,7 @@ const InfoModal = ({ name: data.name, avatar: data.avatar, intro: data.intro, - share: data.share + permission: data.permission }); }, onSuccess() { @@ -97,7 +100,7 @@ const InfoModal = ({ const file = e[0]; if (!file) return; try { - const src = await compressImg({ + const src = await compressImgAndUpload({ file, maxW: 100, maxH: 100 @@ -139,7 +142,7 @@ const InfoModal = ({ > - + 应用介绍 {/* @@ -152,6 +155,16 @@ const InfoModal = ({ bg={'myWhite.600'} {...register('intro')} /> + + {t('user.Permission')} + { + setValue('permission', e); + setRefresh(!refresh); + }} + /> + diff --git a/projects/app/src/pages/app/detail/components/Logs.tsx b/projects/app/src/pages/app/detail/components/Logs.tsx index fa0124979..154b6ea2e 100644 --- a/projects/app/src/pages/app/detail/components/Logs.tsx +++ b/projects/app/src/pages/app/detail/components/Logs.tsx @@ -18,7 +18,7 @@ import { useTranslation } from 'next-i18next'; import { usePagination } from '@/web/common/hooks/usePagination'; import { getAppChatLogs } from '@/web/core/app/api'; import dayjs from 'dayjs'; -import { ChatSourceMap, HUMAN_ICON } from '@/constants/chat'; +import { ChatSourceMap, HUMAN_ICON } from '@fastgpt/global/core/chat/constants'; import { AppLogsListItemType } from '@/types/app'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import ChatBox, { type ComponentRef } from '@/components/ChatBox'; @@ -101,7 +101,7 @@ const Logs = ({ appId }: { appId: string }) => { {logs.map((item) => ( { py={[2, 3]} > - {t('outlink.Copy Iframe')} + {t('outlink.Copy IFrame')} { diff --git a/projects/app/src/pages/app/detail/components/QGSwitch.tsx b/projects/app/src/pages/app/detail/components/QGSwitch.tsx new file mode 100644 index 000000000..94a433492 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/QGSwitch.tsx @@ -0,0 +1,23 @@ +import MyIcon from '@/components/Icon'; +import MyTooltip from '@/components/MyTooltip'; +import { QuestionOutlineIcon } from '@chakra-ui/icons'; +import { Box, Flex, Switch, type SwitchProps } from '@chakra-ui/react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +const QGSwitch = (props: SwitchProps) => { + const { t } = useTranslation(); + return ( + + + {t('core.app.Next Step Guide')} + + + + + + + ); +}; + +export default QGSwitch; diff --git a/projects/app/src/pages/app/detail/components/TTSSelect.tsx b/projects/app/src/pages/app/detail/components/TTSSelect.tsx new file mode 100644 index 000000000..35ee1f5f3 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/TTSSelect.tsx @@ -0,0 +1,94 @@ +import MyIcon from '@/components/Icon'; +import MyTooltip from '@/components/MyTooltip'; +import { QuestionOutlineIcon } from '@chakra-ui/icons'; +import { Box, Flex } from '@chakra-ui/react'; +import React, { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import MySelect from '@/components/Select'; +import { TTSTypeEnum } from '@/constants/app'; +import { Text2SpeechVoiceEnum, openaiTTSModel } from '@fastgpt/global/core/ai/speech/constant'; +import { AppTTSConfigType } from '@/types/app'; +import { useAudioPlay } from '@/web/common/utils/voice'; +import { useLoading } from '@/web/common/hooks/useLoading'; + +const TTSSelect = ({ + value, + onChange +}: { + value: AppTTSConfigType; + onChange: (e: AppTTSConfigType) => void; +}) => { + const { t } = useTranslation(); + const { playAudio, audioLoading } = useAudioPlay({ ttsConfig: value }); + const { Loading } = useLoading(); + + const formatValue = useMemo(() => { + if (!value || !value.type) { + return TTSTypeEnum.none; + } + if (value.type === TTSTypeEnum.none || value.type === TTSTypeEnum.web) { + return value.type; + } + return value.voice; + }, [value]); + + const onclickChange = useCallback( + (e: string) => { + if (e === TTSTypeEnum.none || e === TTSTypeEnum.web) { + onChange({ type: e as `${TTSTypeEnum}` }); + } else { + onChange({ + type: TTSTypeEnum.model, + model: openaiTTSModel, + voice: e as `${Text2SpeechVoiceEnum}`, + speed: 1 + }); + } + }, + [onChange] + ); + + return ( + + + {t('core.app.TTS')} + + + + + {formatValue !== TTSTypeEnum.none && ( + + { + playAudio({ + text: t('core.app.tts.Test Listen Text') + }); + }} + /> + + )} + + + + ); +}; + +export default TTSSelect; diff --git a/projects/app/src/pages/app/detail/index.tsx b/projects/app/src/pages/app/detail/index.tsx index dc954ed0b..6a051d32e 100644 --- a/projects/app/src/pages/app/detail/index.tsx +++ b/projects/app/src/pages/app/detail/index.tsx @@ -1,9 +1,7 @@ import React, { useEffect, useMemo, useCallback } from 'react'; import { useRouter } from 'next/router'; import { Box, Flex, IconButton, useTheme } from '@chakra-ui/react'; -import { useUserStore } from '@/web/support/user/useUserStore'; import dynamic from 'next/dynamic'; -import { defaultApp } from '@/constants/model'; import { useToast } from '@/web/common/hooks/useToast'; import { useQuery } from '@tanstack/react-query'; import { feConfigs } from '@/web/common/system/staticData'; @@ -16,6 +14,7 @@ import PageContainer from '@/components/PageContainer'; import Loading from '@/components/Loading'; import BasicEdit from './components/BasicEdit'; import { serviceSideProps } from '@/web/common/utils/i18n'; +import { useAppStore } from '@/web/core/app/store/useAppStore'; const AdEdit = dynamic(() => import('./components/AdEdit'), { loading: () => @@ -36,7 +35,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => { const theme = useTheme(); const { toast } = useToast(); const { appId } = router.query as { appId: string }; - const { appDetail = defaultApp, loadAppDetail, clearAppModules } = useUserStore(); + const { appDetail, loadAppDetail, clearAppModules } = useAppStore(); const setCurrentTab = useCallback( (tab: `${TabEnum}`) => { diff --git a/projects/app/src/pages/app/list/component/CreateModal.tsx b/projects/app/src/pages/app/list/component/CreateModal.tsx index c6ddcfabb..a34573e2d 100644 --- a/projects/app/src/pages/app/list/component/CreateModal.tsx +++ b/projects/app/src/pages/app/list/component/CreateModal.tsx @@ -13,7 +13,7 @@ import { } from '@chakra-ui/react'; import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; import { useForm } from 'react-hook-form'; -import { compressImg } from '@/web/common/file/utils'; +import { compressImgAndUpload } from '@/web/common/file/controller'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { useToast } from '@/web/common/hooks/useToast'; import { postCreateApp } from '@/web/core/app/api'; @@ -56,7 +56,7 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: ( const file = e[0]; if (!file) return; try { - const src = await compressImg({ + const src = await compressImgAndUpload({ file, maxW: 100, maxH: 100 diff --git a/projects/app/src/pages/app/list/index.tsx b/projects/app/src/pages/app/list/index.tsx index fd9286147..b1fe2f3e3 100644 --- a/projects/app/src/pages/app/list/index.tsx +++ b/projects/app/src/pages/app/list/index.tsx @@ -11,7 +11,6 @@ import { Image } from '@chakra-ui/react'; import { useRouter } from 'next/router'; -import { useUserStore } from '@/web/support/user/useUserStore'; import { useQuery } from '@tanstack/react-query'; import { AddIcon } from '@chakra-ui/icons'; import { delModelById } from '@/web/core/app/api'; @@ -25,13 +24,17 @@ import PageContainer from '@/components/PageContainer'; import Avatar from '@/components/Avatar'; import MyTooltip from '@/components/MyTooltip'; import CreateModal from './component/CreateModal'; +import { useAppStore } from '@/web/core/app/store/useAppStore'; +import PermissionIconText from '@/components/support/permission/IconText'; +import { useUserStore } from '@/web/support/user/useUserStore'; const MyApps = () => { const { toast } = useToast(); const { t } = useTranslation(); const theme = useTheme(); const router = useRouter(); - const { myApps, loadMyApps } = useUserStore(); + const { userInfo } = useUserStore(); + const { myApps, loadMyApps } = useAppStore(); const { openConfirm, ConfirmModal } = useConfirm({ title: '删除提示', content: '确认删除该应用所有信息?' @@ -86,84 +89,103 @@ const MyApps = () => { gridGap={5} > {myApps.map((app) => ( - router.push(`/app/detail?appId=${app._id}`)} + label={userInfo?.team.canWrite ? t('app.To Settings') : t('app.To Chat')} > - - - {app.name} - } - variant={'base'} - borderRadius={'md'} - aria-label={'delete'} - display={['', 'none']} - _hover={{ - bg: 'red.100' - }} - onClick={(e) => { - e.stopPropagation(); - openConfirm(() => onclickDelApp(app._id))(); - }} - /> - - - {app.intro || '这个应用还没写介绍~'} - - - - - } - variant={'base'} - borderRadius={'md'} - aria-label={'delete'} - display={['', 'none']} + { - e.stopPropagation(); - router.push(`/chat?appId=${app._id}`); + onClick={() => { + if (userInfo?.team.canWrite) { + router.push(`/app/detail?appId=${app._id}`); + } else { + router.push(`/chat?appId=${app._id}`); + } }} - /> - + > + + + {app.name} + {app.isOwner && userInfo?.team.canWrite && ( + } + variant={'base'} + borderRadius={'md'} + aria-label={'delete'} + display={['', 'none']} + _hover={{ + bg: 'red.100' + }} + onClick={(e) => { + e.stopPropagation(); + openConfirm(() => onclickDelApp(app._id))(); + }} + /> + )} + + + {app.intro || '这个应用还没写介绍~'} + + + + + + {userInfo?.team.canWrite && ( + + + + } + variant={'base'} + borderRadius={'md'} + aria-label={'delete'} + display={['', 'none']} + _hover={{ + bg: 'myGray.100' + }} + onClick={(e) => { + e.stopPropagation(); + router.push(`/chat?appId=${app._id}`); + }} + /> + )} + + + ))} diff --git a/projects/app/src/pages/chat/components/ChatHeader.tsx b/projects/app/src/pages/chat/components/ChatHeader.tsx index f05e78906..7da3dd1b0 100644 --- a/projects/app/src/pages/chat/components/ChatHeader.tsx +++ b/projects/app/src/pages/chat/components/ChatHeader.tsx @@ -5,7 +5,7 @@ import MyIcon from '@/components/Icon'; import Tag from '@/components/Tag'; import Avatar from '@/components/Avatar'; import ToolMenu from './ToolMenu'; -import { ChatItemType } from '@/types/chat'; +import type { ChatItemType } from '@fastgpt/global/core/chat/type'; import { useRouter } from 'next/router'; const ChatHeader = ({ diff --git a/projects/app/src/pages/chat/components/ChatHistorySlider.tsx b/projects/app/src/pages/chat/components/ChatHistorySlider.tsx index c6af535f7..f46547951 100644 --- a/projects/app/src/pages/chat/components/ChatHistorySlider.tsx +++ b/projects/app/src/pages/chat/components/ChatHistorySlider.tsx @@ -21,6 +21,8 @@ import { useConfirm } from '@/web/common/hooks/useConfirm'; import Tabs from '@/components/Tabs'; import { useUserStore } from '@/web/support/user/useUserStore'; import { useQuery } from '@tanstack/react-query'; +import { useAppStore } from '@/web/core/app/store/useAppStore'; +import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; type HistoryItemType = { id: string; @@ -63,7 +65,8 @@ const ChatHistorySlider = ({ const router = useRouter(); const { t } = useTranslation(); const { isPc } = useSystemStore(); - const { myApps, loadMyApps, userInfo } = useUserStore(); + const { myApps, loadMyApps } = useAppStore(); + const { userInfo } = useUserStore(); const [currentTab, setCurrentTab] = useState<`${TabEnum}`>(TabEnum.history); @@ -76,7 +79,7 @@ const ChatHistorySlider = ({ }); const { openConfirm, ConfirmModal } = useConfirm({ content: isShare - ? t('chat.Confirm to clear share chat histroy') + ? t('chat.Confirm to clear share chat history') : t('chat.Confirm to clear history') }); @@ -94,6 +97,11 @@ const ChatHistorySlider = ({ return loadMyApps(false); }); + const canRouteToDetail = useMemo( + () => appId && userInfo?.team.role !== TeamMemberRoleEnum.visitor, + [appId, userInfo?.team.role] + ); + return ( {isPc && ( - + - appId && + canRouteToDetail && router.replace({ pathname: '/app/detail', query: { appId } diff --git a/projects/app/src/pages/chat/components/SliderApps.tsx b/projects/app/src/pages/chat/components/SliderApps.tsx index 09f3e36c4..0810b03db 100644 --- a/projects/app/src/pages/chat/components/SliderApps.tsx +++ b/projects/app/src/pages/chat/components/SliderApps.tsx @@ -1,16 +1,16 @@ import React from 'react'; import { Flex, Box, IconButton } from '@chakra-ui/react'; import { useRouter } from 'next/router'; -import { useUserStore } from '@/web/support/user/useUserStore'; import { useQuery } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import MyIcon from '@/components/Icon'; import Avatar from '@/components/Avatar'; +import { useAppStore } from '@/web/core/app/store/useAppStore'; const SliderApps = ({ appId }: { appId: string }) => { const { t } = useTranslation(); const router = useRouter(); - const { myApps, loadMyApps } = useUserStore(); + const { myApps, loadMyApps } = useAppStore(); useQuery(['loadModels'], () => loadMyApps(false)); diff --git a/projects/app/src/pages/chat/components/ToolMenu.tsx b/projects/app/src/pages/chat/components/ToolMenu.tsx index 667652748..c5e27af49 100644 --- a/projects/app/src/pages/chat/components/ToolMenu.tsx +++ b/projects/app/src/pages/chat/components/ToolMenu.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; import { useChatBox } from '@/components/ChatBox'; -import { ChatItemType } from '@/types/chat'; +import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; import { Menu, MenuButton, MenuList, MenuItem, Box } from '@chakra-ui/react'; import MyIcon from '@/components/Icon'; import { useRouter } from 'next/router'; diff --git a/projects/app/src/pages/chat/index.tsx b/projects/app/src/pages/chat/index.tsx index 7160687a8..fb76765ac 100644 --- a/projects/app/src/pages/chat/index.tsx +++ b/projects/app/src/pages/chat/index.tsx @@ -19,7 +19,7 @@ import { useLoading } from '@/web/common/hooks/useLoading'; import { useToast } from '@/web/common/hooks/useToast'; import { customAlphabet } from 'nanoid'; const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12); -import type { ChatHistoryItemType } from '@/types/chat'; +import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d'; import { useTranslation } from 'react-i18next'; import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox'; @@ -31,6 +31,7 @@ import ChatHeader from './components/ChatHeader'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { useUserStore } from '@/web/support/user/useUserStore'; import { serviceSideProps } from '@/web/common/utils/i18n'; +import { useAppStore } from '@/web/core/app/store/useAppStore'; const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { const router = useRouter(); @@ -54,7 +55,8 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { chatData, setChatData } = useChatStore(); - const { myApps, loadMyApps, userInfo } = useUserStore(); + const { myApps, loadMyApps } = useAppStore(); + const { userInfo } = useUserStore(); const { isPc } = useSystemStore(); const { Loading, setIsLoading } = useLoading(); @@ -188,7 +190,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { [setIsLoading, setChatData, router, setLastChatAppId, setLastChatId, toast] ); // 初始化聊天框 - useQuery(['init', appId, chatId], () => { + useQuery(['init', { appId, chatId }], () => { // pc: redirect to latest model chat if (!appId && lastChatAppId) { return router.replace({ diff --git a/projects/app/src/pages/chat/share.tsx b/projects/app/src/pages/chat/share.tsx index 3d6433634..db84c0958 100644 --- a/projects/app/src/pages/chat/share.tsx +++ b/projects/app/src/pages/chat/share.tsx @@ -11,7 +11,7 @@ import { useShareChatStore, defaultHistory } from '@/web/core/chat/storeShareCha import SideBar from '@/components/SideBar'; import { gptMessage2ChatType } from '@/utils/adapt'; import { getErrText } from '@fastgpt/global/common/error/utils'; -import { ChatSiteItemType } from '@/types/chat'; +import type { ChatSiteItemType } from '@fastgpt/global/core/chat/type.d'; import { customAlphabet } from 'nanoid'; const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12); diff --git a/projects/app/src/pages/dataset/detail/components/CollectionCard.tsx b/projects/app/src/pages/dataset/detail/components/CollectionCard.tsx index b97d4b696..b1de0dd3f 100644 --- a/projects/app/src/pages/dataset/detail/components/CollectionCard.tsx +++ b/projects/app/src/pages/dataset/detail/components/CollectionCard.tsx @@ -50,6 +50,8 @@ import { useDrag } from '@/web/common/hooks/useDrag'; import SelectCollections from '@/web/core/dataset/components/SelectCollections'; import { useToast } from '@/web/common/hooks/useToast'; import MyTooltip from '@/components/MyTooltip'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; const FileImportModal = dynamic(() => import('./Import/ImportModal'), {}); @@ -62,6 +64,7 @@ const CollectionCard = () => { const { t } = useTranslation(); const { Loading } = useLoading(); const { isPc } = useSystemStore(); + const { userInfo } = useUserStore(); const [searchText, setSearchText] = useState(''); const { setLoading } = useSystemStore(); @@ -271,67 +274,69 @@ const CollectionCard = () => { /> )} - - - - {t('dataset.collections.Create And Import')} - - - } - menuList={[ - { - child: ( - - - {t('Folder')} + + + {t('dataset.collections.Create And Import')} - ), - onClick: () => setEditFolderData({}) - }, - { - child: ( - - - {t('dataset.Create Virtual File')} - - ), - onClick: () => { - onOpenCreateVirtualFileModal({ - defaultVal: '', - onSuccess: (name) => onCreateVirtualFile({ name }) - }); - } - }, - { - child: ( - - - {t('dataset.File Input')} - - ), - onClick: onOpenFileImportModal + } - ]} - /> + menuList={[ + { + child: ( + + + {t('Folder')} + + ), + onClick: () => setEditFolderData({}) + }, + { + child: ( + + + {t('dataset.Create Virtual File')} + + ), + onClick: () => { + onOpenCreateVirtualFileModal({ + defaultVal: '', + onSuccess: (name) => onCreateVirtualFile({ name }) + }); + } + }, + { + child: ( + + + {t('dataset.File Input')} + + ), + onClick: onOpenFileImportModal + } + ]} + /> + )} @@ -436,85 +441,87 @@ const CollectionCard = () => { ))} diff --git a/projects/app/src/pages/dataset/detail/components/DataCard.tsx b/projects/app/src/pages/dataset/detail/components/DataCard.tsx index cd7b1377a..6e7a0f813 100644 --- a/projects/app/src/pages/dataset/detail/components/DataCard.tsx +++ b/projects/app/src/pages/dataset/detail/components/DataCard.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useState, useRef, useMemo } from 'react'; -import { Box, Card, IconButton, Flex, Grid, Image, Button } from '@chakra-ui/react'; +import { Box, Card, IconButton, Flex, Grid, Button } from '@chakra-ui/react'; import { usePagination } from '@/web/common/hooks/usePagination'; import { getDatasetDataList, @@ -17,21 +17,22 @@ import { useRouter } from 'next/router'; import MyIcon from '@/components/Icon'; import MyInput from '@/components/MyInput'; import { useLoading } from '@/web/common/hooks/useLoading'; -import { getCollectionIcon } from '@fastgpt/global/core/dataset/utils'; import InputDataModal, { RawSourceText, type InputDataType } from '../components/InputDataModal'; import type { DatasetDataListItemType } from '@/global/core/dataset/response.d'; import { TabEnum } from '..'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; const DataCard = () => { const BoxRef = useRef(null); const lastSearch = useRef(''); const router = useRouter(); + const { userInfo } = useUserStore(); const { collectionId = '' } = router.query as { collectionId: string }; const { Loading, setIsLoading } = useLoading({ defaultLoading: true }); const { t } = useTranslation(); const [searchText, setSearchText] = useState(''); const { toast } = useToast(); - const [isDeleting, setIsDeleting] = useState(false); const { openConfirm, ConfirmModal } = useConfirm({ content: t('dataset.Confirm to delete the data') }); @@ -74,6 +75,11 @@ const DataCard = () => { getDatasetCollectionById(collectionId) ); + const canWrite = useMemo( + () => userInfo?.team?.role !== TeamMemberRoleEnum.visitor && !!collection?.canWrite, + [collection?.canWrite, userInfo?.team?.role] + ); + return ( @@ -112,24 +118,25 @@ const DataCard = () => { - - - + {canWrite && ( + + + + )} @@ -180,7 +187,6 @@ const DataCard = () => { if (!collection) return; setEditInputData({ id: item.id, - datasetId: collection.datasetId, collectionId: collection._id, q: item.q, a: item.a, @@ -206,34 +212,35 @@ const DataCard = () => { ID:{item.id} - } - variant={'base'} - colorScheme={'gray'} - aria-label={'delete'} - size={'xs'} - borderRadius={'md'} - _hover={{ color: 'red.600' }} - isLoading={isDeleting} - onClick={(e) => { - e.stopPropagation(); - openConfirm(async () => { - try { - setIsDeleting(true); - await delOneDatasetDataById(item.id); - getData(pageNum); - } catch (error) { - toast({ - title: getErrText(error), - status: 'error' - }); - } - setIsDeleting(false); - })(); - }} - /> + {canWrite && ( + } + variant={'base'} + colorScheme={'gray'} + aria-label={'delete'} + size={'xs'} + borderRadius={'md'} + _hover={{ color: 'red.600' }} + onClick={(e) => { + e.stopPropagation(); + openConfirm(async () => { + try { + setIsLoading(true); + await delOneDatasetDataById(item.id); + getData(pageNum); + } catch (error) { + toast({ + title: getErrText(error), + status: 'error' + }); + } + setIsLoading(false); + })(); + }} + /> + )} ))} @@ -248,17 +255,18 @@ const DataCard = () => { - 知识库空空如也 + 内容空空的,快创建一个吧! )} {editInputData !== undefined && collection && ( setEditInputData(undefined)} onSuccess={() => getData(pageNum)} + canWrite={canWrite} /> )} diff --git a/projects/app/src/pages/dataset/detail/components/Import/Chunk.tsx b/projects/app/src/pages/dataset/detail/components/Import/Chunk.tsx index 066648a38..ea0f707d4 100644 --- a/projects/app/src/pages/dataset/detail/components/Import/Chunk.tsx +++ b/projects/app/src/pages/dataset/detail/components/Import/Chunk.tsx @@ -10,7 +10,7 @@ import { NumberDecrementStepper } from '@chakra-ui/react'; import { useConfirm } from '@/web/common/hooks/useConfirm'; -import { formatPrice } from '@fastgpt/global/common/bill/tools'; +import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; import MyTooltip from '@/components/MyTooltip'; import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { useDatasetStore } from '@/web/core/dataset/store/dataset'; diff --git a/projects/app/src/pages/dataset/detail/components/Import/FileSelect.tsx b/projects/app/src/pages/dataset/detail/components/Import/FileSelect.tsx index 27fceb0d8..dcde2019a 100644 --- a/projects/app/src/pages/dataset/detail/components/Import/FileSelect.tsx +++ b/projects/app/src/pages/dataset/detail/components/Import/FileSelect.tsx @@ -5,13 +5,13 @@ import { useToast } from '@/web/common/hooks/useToast'; import { splitText2Chunks } from '@/global/common/string/tools'; import { simpleText } from '@fastgpt/global/common/string/tools'; import { - uploadFiles, fileDownload, readCsvContent, readTxtContent, readPdfContent, readDocContent } from '@/web/common/file/utils'; +import { uploadFiles } from '@/web/common/file/controller'; import { Box, Flex, useDisclosure, type BoxProps } from '@chakra-ui/react'; import React, { DragEvent, useCallback, useState } from 'react'; import { useTranslation } from 'next-i18next'; @@ -108,13 +108,18 @@ const FileSelect = ({ if (!icon) continue; // upload file - const filesId = await uploadFiles([file], { datasetId: datasetDetail._id }, (percent) => { - if (percent < 100) { - setSelectingText( - t('file.Uploading', { name: file.name.slice(0, 30), percent }) || '' - ); - } else { - setSelectingText(t('file.Parse', { name: file.name.slice(0, 30) }) || ''); + const filesId = await uploadFiles({ + files: [file], + bucketName: 'dataset', + metadata: { datasetId: datasetDetail._id }, + percentListen: (percent) => { + if (percent < 100) { + setSelectingText( + t('file.Uploading', { name: file.name.slice(0, 30), percent }) || '' + ); + } else { + setSelectingText(t('file.Parse', { name: file.name.slice(0, 30) }) || ''); + } } }); const fileId = filesId[0]; @@ -243,7 +248,11 @@ const FileSelect = ({ type: txtBlob.type, lastModified: new Date().getTime() }); - const fileIds = await uploadFiles([txtFile], { datasetId: datasetDetail._id }); + const fileIds = await uploadFiles({ + files: [txtFile], + bucketName: 'dataset', + metadata: { datasetId: datasetDetail._id } + }); const splitRes = splitText2Chunks({ text: content, diff --git a/projects/app/src/pages/dataset/detail/components/Import/ImportModal.tsx b/projects/app/src/pages/dataset/detail/components/Import/ImportModal.tsx index e2d4af22e..4513ecd70 100644 --- a/projects/app/src/pages/dataset/detail/components/Import/ImportModal.tsx +++ b/projects/app/src/pages/dataset/detail/components/Import/ImportModal.tsx @@ -45,7 +45,7 @@ const ImportData = ({ mode: TrainingModeEnum.index }, [ImportTypeEnum.qa]: { - defaultChunkLen: qaModel?.maxToken * 0.5 || 8000, + defaultChunkLen: qaModel?.maxContext * 0.5 || 8000, unitPrice: qaModel?.price || 3, mode: TrainingModeEnum.qa }, diff --git a/projects/app/src/pages/dataset/detail/components/Import/Provider.tsx b/projects/app/src/pages/dataset/detail/components/Import/Provider.tsx index bad9d8818..b3386eb1b 100644 --- a/projects/app/src/pages/dataset/detail/components/Import/Provider.tsx +++ b/projects/app/src/pages/dataset/detail/components/Import/Provider.tsx @@ -11,7 +11,7 @@ import React, { import FileSelect, { FileItemType, Props as FileSelectProps } from './FileSelect'; import { useRequest } from '@/web/common/hooks/useRequest'; import { postDatasetCollection } from '@/web/core/dataset/api'; -import { formatPrice } from '@fastgpt/global/common/bill/tools'; +import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; import { splitText2Chunks } from '@/global/common/string/tools'; import { useToast } from '@/web/common/hooks/useToast'; import { getErrText } from '@fastgpt/global/common/error/utils'; @@ -21,7 +21,7 @@ import { CloseIcon } from '@chakra-ui/icons'; import DeleteIcon, { hoverDeleteStyles } from '@/components/Icon/delete'; import MyIcon from '@/components/Icon'; import { chunksUpload } from '@/web/core/dataset/utils'; -import { postCreateTrainingBill } from '@/web/common/bill/api'; +import { postCreateTrainingBill } from '@/web/support/wallet/bill/api'; import { useTranslation } from 'react-i18next'; import { ImportTypeEnum } from './ImportModal'; @@ -129,19 +129,19 @@ const Provider = ({ let totalInsertion = 0; for await (const file of files) { const chunks = file.chunks; + // create training bill + const billId = await postCreateTrainingBill({ + name: t('dataset.collections.Create Training Data', { filename: file.filename }) + }); // create a file collection and training bill - const [collectionId, billId] = await Promise.all([ - postDatasetCollection({ - datasetId, - parentId, - name: file.filename, - type: file.type, - metadata: file.metadata - }), - postCreateTrainingBill({ - name: t('dataset.collections.Create Training Data', { filename: file.filename }) - }) - ]); + const collectionId = await postDatasetCollection({ + datasetId, + parentId, + name: file.filename, + type: file.type, + metadata: file.metadata + }); + // upload data const { insertLen } = await chunksUpload({ collectionId, diff --git a/projects/app/src/pages/dataset/detail/components/Import/QA.tsx b/projects/app/src/pages/dataset/detail/components/Import/QA.tsx index 073e64af2..23c2cda8f 100644 --- a/projects/app/src/pages/dataset/detail/components/Import/QA.tsx +++ b/projects/app/src/pages/dataset/detail/components/Import/QA.tsx @@ -1,7 +1,7 @@ import React, { useState, useMemo } from 'react'; import { Box, Flex, Button, Input } from '@chakra-ui/react'; import { useConfirm } from '@/web/common/hooks/useConfirm'; -import { formatPrice } from '@fastgpt/global/common/bill/tools'; +import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; import MyTooltip from '@/components/MyTooltip'; import { QuestionOutlineIcon, InfoOutlineIcon } from '@chakra-ui/icons'; import { Prompt_AgentQA } from '@/global/core/prompt/agent'; diff --git a/projects/app/src/pages/dataset/detail/components/Info.tsx b/projects/app/src/pages/dataset/detail/components/Info.tsx index 9790dee96..5df20f8a9 100644 --- a/projects/app/src/pages/dataset/detail/components/Info.tsx +++ b/projects/app/src/pages/dataset/detail/components/Info.tsx @@ -15,11 +15,13 @@ import { useToast } from '@/web/common/hooks/useToast'; import { useDatasetStore } from '@/web/core/dataset/store/dataset'; import { useConfirm } from '@/web/common/hooks/useConfirm'; import { UseFormReturn } from 'react-hook-form'; -import { compressImg } from '@/web/common/file/utils'; +import { compressImgAndUpload } from '@/web/common/file/controller'; import type { DatasetItemType } from '@/types/core/dataset'; import Avatar from '@/components/Avatar'; import Tag from '@/components/Tag'; import MyTooltip from '@/components/MyTooltip'; +import { useTranslation } from 'react-i18next'; +import PermissionRadio from '@/components/support/permission/Radio'; export interface ComponentRef { initInput: (tags: string) => void; @@ -29,6 +31,7 @@ const Info = ( { datasetId, form }: { datasetId: string; form: UseFormReturn }, ref: ForwardedRef ) => { + const { t } = useTranslation(); const { getValues, formState, setValue, register, handleSubmit } = form; const InputRef = useRef(null); @@ -115,7 +118,7 @@ const Info = ( const file = e[0]; if (!file) return; try { - const src = await compressImg({ + const src = await compressImgAndUpload({ file, maxW: 100, maxH: 100 @@ -220,6 +223,23 @@ const Info = ( ))} + {datasetDetail.isOwner && ( + + + {t('user.Permission')} + + + { + setValue('permission', e); + setRefresh(!refresh); + }} + /> + + + )} + - } - aria-label={''} - variant={'outline'} - size={'sm'} - _hover={{ - color: 'red.600', - borderColor: 'red.600' - }} - onClick={openConfirm(onclickDelKb)} - /> + {datasetDetail.isOwner && ( + } + aria-label={''} + variant={'outline'} + size={'sm'} + _hover={{ + color: 'red.600', + borderColor: 'red.600' + }} + onClick={openConfirm(onclickDelKb)} + /> + )} diff --git a/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx b/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx index dda063a41..a01b56a6d 100644 --- a/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx +++ b/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx @@ -15,7 +15,7 @@ import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { useQuery } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { useDatasetStore } from '@/web/core/dataset/store/dataset'; -import { getFileAndOpen } from '@/web/common/file/utils'; +import { getFileAndOpen } from '@/web/core/dataset/utils'; import { strIsLink } from '@fastgpt/global/common/string/tools'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import type { SetOneDatasetDataProps } from '@/global/core/api/datasetReq'; @@ -39,17 +39,18 @@ const InputDataModal = ({ onDelete, datasetId, defaultValues = { - datasetId: '', collectionId: '', sourceId: '', sourceName: '' - } + }, + canWrite }: { onClose: () => void; onSuccess: (data: SetOneDatasetDataProps) => void; onDelete?: () => void; datasetId: string; defaultValues: InputDataType; + canWrite: boolean; }) => { const { t } = useTranslation(); const { toast } = useToast(); @@ -223,7 +224,7 @@ const InputDataModal = ({ /> - {defaultValues.id && onDelete && ( + {defaultValues.id && onDelete && canWrite && ( } @@ -259,13 +260,16 @@ const InputDataModal = ({ - + + + @@ -325,7 +329,7 @@ export function RawSourceText({ > - {sourceName || t('common.Unknow Source')} + {sourceName || t('common.UnKnow Source')} diff --git a/projects/app/src/pages/dataset/detail/components/Test.tsx b/projects/app/src/pages/dataset/detail/components/Test.tsx index 19b30f671..587a37607 100644 --- a/projects/app/src/pages/dataset/detail/components/Test.tsx +++ b/projects/app/src/pages/dataset/detail/components/Test.tsx @@ -206,7 +206,6 @@ const Test = ({ datasetId }: { datasetId: string }) => { setEditInputData({ id: data.id, - datasetId: data.datasetId, collectionId: data.collectionId, q: data.q, a: data.a, @@ -256,7 +255,8 @@ const Test = ({ datasetId }: { datasetId: string }) => { {!!editInputData && ( setEditInputData(undefined)} onSuccess={(data) => { diff --git a/projects/app/src/pages/dataset/detail/index.tsx b/projects/app/src/pages/dataset/detail/index.tsx index 0df964a11..db3a94b45 100644 --- a/projects/app/src/pages/dataset/detail/index.tsx +++ b/projects/app/src/pages/dataset/detail/index.tsx @@ -17,13 +17,14 @@ import Avatar from '@/components/Avatar'; import Info from './components/Info'; import { serviceSideProps } from '@/web/common/utils/i18n'; import { useTranslation } from 'react-i18next'; -import { getTrainingQueueLen, delDatasetEmptyFiles } from '@/web/core/dataset/api'; +import { getTrainingQueueLen } from '@/web/core/dataset/api'; import MyTooltip from '@/components/MyTooltip'; import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { feConfigs } from '@/web/common/system/staticData'; import Script from 'next/script'; import CollectionCard from './components/CollectionCard'; import { useDatasetStore } from '@/web/core/dataset/store/dataset'; +import { useUserStore } from '@/web/support/user/useUserStore'; const DataCard = dynamic(() => import('./components/DataCard'), { ssr: false @@ -47,12 +48,15 @@ const Detail = ({ datasetId, currentTab }: { datasetId: string; currentTab: `${T const router = useRouter(); const { isPc } = useSystemStore(); const { datasetDetail, loadDatasetDetail } = useDatasetStore(); + const { userInfo } = useUserStore(); - const tabList = useRef([ + const tabList = [ { label: '数据集', id: TabEnum.collectionCard, icon: 'overviewLight' }, { label: '搜索测试', id: TabEnum.test, icon: 'kbTest' }, - { label: '配置', id: TabEnum.info, icon: 'settingLight' } - ]); + ...(userInfo?.team.canWrite && datasetDetail.isOwner + ? [{ label: '配置', id: TabEnum.info, icon: 'settingLight' }] + : []) + ]; const setCurrentTab = useCallback( (tab: `${TabEnum}`) => { @@ -88,14 +92,6 @@ const Detail = ({ datasetId, currentTab }: { datasetId: string; currentTab: `${T refetchInterval: 10000 }); - useEffect(() => { - return () => { - try { - delDatasetEmptyFiles(datasetId); - } catch (error) {} - }; - }, [datasetId]); - return ( <> @@ -120,7 +116,7 @@ const Detail = ({ datasetId, currentTab }: { datasetId: string; currentTab: `${T mx={'auto'} mt={2} w={'100%'} - list={tabList.current} + list={tabList} activeId={currentTab} onChange={(e: any) => { setCurrentTab(e); @@ -169,7 +165,7 @@ const Detail = ({ datasetId, currentTab }: { datasetId: string; currentTab: `${T m={'auto'} w={'260px'} size={isPc ? 'md' : 'sm'} - list={tabList.current.map((item) => ({ + list={tabList.map((item) => ({ id: item.id, label: item.label }))} diff --git a/projects/app/src/pages/dataset/list/component/CreateModal.tsx b/projects/app/src/pages/dataset/list/component/CreateModal.tsx index 8339a1664..acfd06027 100644 --- a/projects/app/src/pages/dataset/list/component/CreateModal.tsx +++ b/projects/app/src/pages/dataset/list/component/CreateModal.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useState, useRef } from 'react'; import { Box, Flex, Button, ModalHeader, ModalFooter, ModalBody, Input } from '@chakra-ui/react'; import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; import { useForm } from 'react-hook-form'; -import { compressImg } from '@/web/common/file/utils'; +import { compressImgAndUpload } from '@/web/common/file/controller'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { useToast } from '@/web/common/hooks/useToast'; import { useRouter } from 'next/router'; @@ -27,7 +27,7 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st defaultValues: { avatar: '/icon/logo.svg', name: '', - tags: [], + tags: '', vectorModel: vectorModelList[0].model, type: 'dataset', parentId @@ -45,7 +45,7 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st const file = e[0]; if (!file) return; try { - const src = await compressImg({ + const src = await compressImgAndUpload({ file, maxW: 100, maxH: 100 @@ -135,13 +135,14 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st placeholder={'标签,使用空格分割。'} maxLength={30} onChange={(e) => { - setValue('tags', e.target.value.split(' ')); + setValue('tags', e.target.value); setRefresh(!refresh); }} /> {getValues('tags') + .split(' ') .filter((item) => item) .map((item, i) => ( diff --git a/projects/app/src/pages/dataset/list/index.tsx b/projects/app/src/pages/dataset/list/index.tsx index 5183ddfe0..90a9f3146 100644 --- a/projects/app/src/pages/dataset/list/index.tsx +++ b/projects/app/src/pages/dataset/list/index.tsx @@ -36,6 +36,10 @@ import { useEditTitle } from '@/web/common/hooks/useEditTitle'; import { feConfigs } from '@/web/common/system/staticData'; import EditFolderModal, { useEditFolder } from '../component/EditFolderModal'; import { useDrag } from '@/web/common/hooks/useDrag'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; +import PermissionIconText from '@/components/support/permission/IconText'; +import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; const CreateModal = dynamic(() => import('./component/CreateModal'), { ssr: false }); const MoveModal = dynamic(() => import('./component/MoveModal'), { ssr: false }); @@ -46,6 +50,7 @@ const Kb = () => { const router = useRouter(); const { parentId } = router.query as { parentId: string }; const { setLoading } = useSystemStore(); + const { userInfo } = useUserStore(); const DeleteTipsMap = useRef({ [DatasetTypeEnum.folder]: t('dataset.deleteFolderTips'), @@ -162,50 +167,51 @@ const Kb = () => { )} - - - - - {t('Create New')} - - - } - menuList={[ - { - child: ( - - - {t('Folder')} + + + {t('Create New')} - ), - onClick: () => setEditFolderData({}) - }, - { - child: ( - - - {t('Dataset')} - - ), - onClick: onOpenCreateModal + } - ]} - /> + menuList={[ + { + child: ( + + + {t('Folder')} + + ), + onClick: () => setEditFolderData({}) + }, + { + child: ( + + + {t('Dataset')} + + ), + onClick: onOpenCreateModal + } + ]} + /> + )} { } }} > - { - e.stopPropagation(); - }} - > - - - } - menuList={[ - { - child: ( - - - {t('Rename')} - - ), - onClick: () => - onOpenTitleModal({ - defaultVal: dataset.name, - onSuccess: (val) => { - if (val === dataset.name || !val) return; - updateDataset({ id: dataset._id, name: val }); + _hover={{ + color: 'myBlue.600', + '& .icon': { + bg: 'myGray.100' } - }) - }, - { - child: ( - - - {t('Move')} - - ), - onClick: () => setMoveDataId(dataset._id) - }, - { - child: ( - - - {t('Export')} - - ), - onClick: () => onclickExport(dataset._id) - }, - { - child: ( - - - {t('common.Delete')} - - ), - onClick: () => { - openConfirm( - () => onclickDelDataset(dataset._id), - undefined, - DeleteTipsMap.current[dataset.type] - )(); - } + }} + onClick={(e) => { + e.stopPropagation(); + }} + > + + } - ]} - /> + menuList={[ + ...(dataset.permission === PermissionTypeEnum.private + ? [ + { + child: ( + + + {t('permission.Set Public')} + + ), + onClick: () => { + updateDataset({ + id: dataset._id, + permission: PermissionTypeEnum.public + }); + } + } + ] + : [ + { + child: ( + + + {t('permission.Set Private')} + + ), + onClick: () => { + updateDataset({ + id: dataset._id, + permission: PermissionTypeEnum.private + }); + } + } + ]), + { + child: ( + + + {t('Rename')} + + ), + onClick: () => + onOpenTitleModal({ + defaultVal: dataset.name, + onSuccess: (val) => { + if (val === dataset.name || !val) return; + updateDataset({ id: dataset._id, name: val }); + } + }) + }, + { + child: ( + + + {t('Move')} + + ), + onClick: () => setMoveDataId(dataset._id) + }, + { + child: ( + + + {t('Export')} + + ), + onClick: () => onclickExport(dataset._id) + }, + { + child: ( + + + {t('common.Delete')} + + ), + onClick: () => { + openConfirm( + () => onclickDelDataset(dataset._id), + undefined, + DeleteTipsMap.current[dataset.type] + )(); + } + } + ]} + /> + )} @@ -372,14 +413,20 @@ const Kb = () => { - {dataset.tags.map((tag, i) => ( - - {tag} - - ))} + {dataset.tags + .split(' ') + .filter((item) => item) + .map((tag, i) => ( + + {tag} + + ))} - + + + + {dataset.type === DatasetTypeEnum.folder ? ( {t('Folder')} ) : ( @@ -419,7 +466,7 @@ const Kb = () => { name, type: DatasetTypeEnum.folder, avatar: FolderAvatarSrc, - tags: [] + tags: '' }); } refetch(); diff --git a/projects/app/src/pages/login/components/LoginForm.tsx b/projects/app/src/pages/login/components/LoginForm.tsx index d5892180f..0ce422b79 100644 --- a/projects/app/src/pages/login/components/LoginForm.tsx +++ b/projects/app/src/pages/login/components/LoginForm.tsx @@ -2,7 +2,8 @@ import React, { useState, Dispatch, useCallback, useRef } from 'react'; import { FormControl, Flex, Input, Button, FormErrorMessage, Box, Link } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; import { useRouter } from 'next/router'; -import { OAuthEnum, PageTypeEnum } from '@/constants/user'; +import { PageTypeEnum } from '@/constants/user'; +import { OAuthEnum } from '@fastgpt/global/support/user/constant'; import { postLogin } from '@/web/support/user/api'; import type { ResLogin } from '@/global/support/api/userRes'; import { useToast } from '@/web/common/hooks/useToast'; @@ -10,6 +11,7 @@ import { feConfigs } from '@/web/common/system/staticData'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import MyIcon from '@/components/Icon'; import { customAlphabet } from 'nanoid'; +import { useUserStore } from '@/web/support/user/useUserStore'; const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 8); interface Props { diff --git a/projects/app/src/pages/plugin/list/component/EditModal.tsx b/projects/app/src/pages/plugin/list/component/EditModal.tsx index 296854a04..eb7f55bf6 100644 --- a/projects/app/src/pages/plugin/list/component/EditModal.tsx +++ b/projects/app/src/pages/plugin/list/component/EditModal.tsx @@ -11,7 +11,7 @@ import { } from '@chakra-ui/react'; import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; import { useForm } from 'react-hook-form'; -import { compressImg } from '@/web/common/file/utils'; +import { compressImgAndUpload } from '@/web/common/file/controller'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { useToast } from '@/web/common/hooks/useToast'; import { useRouter } from 'next/router'; @@ -72,7 +72,7 @@ const CreateModal = ({ const file = e[0]; if (!file) return; try { - const src = await compressImg({ + const src = await compressImgAndUpload({ file, maxW: 100, maxH: 100 diff --git a/projects/app/src/web/common/plusApi/censor.ts b/projects/app/src/service/common/censor/index.ts similarity index 100% rename from projects/app/src/web/common/plusApi/censor.ts rename to projects/app/src/service/common/censor/index.ts diff --git a/projects/app/src/service/common/tiktoken.ts b/projects/app/src/service/common/tiktoken.ts index 7cc3a2a34..1136faf0b 100644 --- a/projects/app/src/service/common/tiktoken.ts +++ b/projects/app/src/service/common/tiktoken.ts @@ -1,5 +1,5 @@ -import { ChatItemType } from '@/types/chat'; -import { ChatRoleEnum } from '@/constants/chat'; +import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import type { NextApiResponse } from 'next'; import { countMessagesTokens, countPromptTokens } from '@/global/common/tiktoken'; import { adaptRole_Chat2Message } from '@/utils/common/adapt/message'; diff --git a/projects/app/src/service/core/ai/model.ts b/projects/app/src/service/core/ai/model.ts index 5f9844c33..8e9cd38b2 100644 --- a/projects/app/src/service/core/ai/model.ts +++ b/projects/app/src/service/core/ai/model.ts @@ -1,15 +1,17 @@ import { + defaultAudioSpeechModels, defaultChatModels, defaultCQModels, defaultExtractModels, defaultQAModels, defaultQGModels, defaultVectorModels -} from '@/constants/model'; +} from '@fastgpt/global/core/ai/model'; export const getChatModel = (model?: string) => { return ( (global.chatModels || defaultChatModels).find((item) => item.model === model) || + global.chatModels?.[0] || defaultChatModels[0] ); }; @@ -50,6 +52,14 @@ export const getVectorModel = (model?: string) => { ); }; +export function getAudioSpeechModel(model?: string) { + return ( + global.audioSpeechModels.find((item) => item.model === model) || + global.audioSpeechModels?.[0] || + defaultAudioSpeechModels[0] + ); +} + export enum ModelTypeEnum { chat = 'chat', qa = 'qa', diff --git a/projects/app/src/service/core/ai/vector.ts b/projects/app/src/service/core/ai/vector.ts new file mode 100644 index 000000000..a8f2a17d9 --- /dev/null +++ b/projects/app/src/service/core/ai/vector.ts @@ -0,0 +1,70 @@ +import { getAIApi } from '@fastgpt/service/core/ai/config'; + +export type GetVectorProps = { + model: string; + input: string | string[]; +}; + +// text to vector +export async function getVectorsByText({ + model = 'text-embedding-ada-002', + input +}: GetVectorProps) { + try { + if (typeof input === 'string' && !input) { + return Promise.reject({ + code: 500, + message: 'input is empty' + }); + } else if (Array.isArray(input)) { + for (let i = 0; i < input.length; i++) { + if (!input[i]) { + return Promise.reject({ + code: 500, + message: 'input array is empty' + }); + } + } + } + + // 获取 chatAPI + const ai = getAIApi(); + + // 把输入的内容转成向量 + const result = await ai.embeddings + .create({ + model, + input + }) + .then(async (res) => { + if (!res.data) { + return Promise.reject('Embedding API 404'); + } + if (!res?.data?.[0]?.embedding) { + console.log(res?.data); + // @ts-ignore + return Promise.reject(res.data?.err?.message || 'Embedding API Error'); + } + return { + tokenLen: res.usage.total_tokens || 0, + vectors: await Promise.all(res.data.map((item) => unityDimensional(item.embedding))) + }; + }); + + return result; + } catch (error) { + console.log(`Embedding Error`, error); + + return Promise.reject(error); + } +} + +function unityDimensional(vector: number[]) { + if (vector.length > 1536) return Promise.reject('向量维度不能超过 1536'); + let resultVector = vector; + const vectorLen = vector.length; + + const zeroVector = new Array(1536 - vectorLen).fill(0); + + return resultVector.concat(zeroVector); +} diff --git a/projects/app/src/service/core/dataset/data/controller.ts b/projects/app/src/service/core/dataset/data/controller.ts new file mode 100644 index 000000000..9073fc618 --- /dev/null +++ b/projects/app/src/service/core/dataset/data/controller.ts @@ -0,0 +1,168 @@ +import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant'; +import { getVectorsByText } from '@/service/core/ai/vector'; +import { PgClient } from '@fastgpt/service/common/pg'; +import { delay } from '@/utils/tools'; +import { + DatasetDataItemType, + PgDataItemType, + PgRawDataItemType +} from '@fastgpt/global/core/dataset/type'; +import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; + +export async function formatPgRawData(data: PgRawDataItemType) { + return { + id: data.id, + q: data.q, + a: data.a, + teamId: data.team_id, + tmbId: data.tmb_id, + datasetId: data.dataset_id, + collectionId: data.collection_id + }; +} + +/* get */ +export async function getDatasetPgData({ id }: { id: string }): Promise { + const { rows } = await PgClient.select(PgDatasetTableName, { + fields: ['id', 'q', 'a', 'team_id', 'tmb_id', 'dataset_id', 'collection_id'], + where: [['id', id]], + limit: 1 + }); + const row = rows[0]; + if (!row) return Promise.reject('Data not found'); + return formatPgRawData(row); +} + +export async function getPgDataWithCollection({ + pgDataList +}: { + pgDataList: PgRawDataItemType[]; +}): Promise { + const collections = await MongoDatasetCollection.find( + { + _id: { $in: pgDataList.map((item) => item.collection_id) } + }, + '_id name datasetId metadata' + ).lean(); + + return pgDataList.map((item) => { + const collection = collections.find( + (collection) => String(collection._id) === item.collection_id + ); + return { + id: item.id, + q: item.q, + a: item.a, + datasetId: collection?.datasetId || '', + collectionId: item.collection_id, + sourceName: collection?.name || '', + sourceId: collection?.metadata?.fileId || collection?.metadata?.rawLink + }; + }); +} + +type Props = { + q: string; + a?: string; + model: string; +}; + +/** + * update a or a + */ +export async function updateData2Dataset({ dataId, q, a = '', model }: Props & { dataId: string }) { + const { vectors = [], tokenLen = 0 } = await (async () => { + if (q) { + return getVectorsByText({ + input: [q], + model + }); + } + return { vectors: [[]], tokenLen: 0 }; + })(); + + await PgClient.update(PgDatasetTableName, { + where: [['id', dataId]], + values: [ + { key: 'a', value: a.replace(/'/g, '"') }, + ...(q + ? [ + { key: 'q', value: q.replace(/'/g, '"') }, + { key: 'vector', value: `[${vectors[0]}]` } + ] + : []) + ] + }); + + return { + vectors, + tokenLen + }; +} + +/* insert data to pg */ +export async function insertData2Dataset({ + teamId, + tmbId, + datasetId, + collectionId, + q, + a = '', + model +}: Props & { + teamId: string; + tmbId: string; + datasetId: string; + collectionId: string; +}) { + if (!q || !datasetId || !collectionId || !model) { + return Promise.reject('q, datasetId, collectionId, model is required'); + } + const { vectors, tokenLen } = await getVectorsByText({ + model, + input: [q] + }); + + let retry = 2; + async function insertPg(): Promise { + try { + const { rows } = await PgClient.insert(PgDatasetTableName, { + values: [ + [ + { key: 'vector', value: `[${vectors[0]}]` }, + { key: 'team_id', value: String(teamId) }, + { key: 'tmb_id', value: String(tmbId) }, + { key: 'q', value: q }, + { key: 'a', value: a }, + { key: 'dataset_id', value: datasetId }, + { key: 'collection_id', value: collectionId } + ] + ] + }); + return rows[0].id; + } catch (error) { + if (--retry < 0) { + return Promise.reject(error); + } + await delay(500); + return insertPg(); + } + } + const insertId = await insertPg(); + + return { + insertId, + tokenLen, + vectors + }; +} + +/** + * delete data by collectionIds + */ +export async function delDataByCollectionId({ collectionIds }: { collectionIds: string[] }) { + const ids = collectionIds.map((item) => String(item)); + return PgClient.delete(PgDatasetTableName, { + where: [`collection_id IN ('${ids.join("','")}')`] + }); +} diff --git a/projects/app/src/service/core/dataset/data/utils.ts b/projects/app/src/service/core/dataset/data/utils.ts index 2634504dc..813628079 100644 --- a/projects/app/src/service/core/dataset/data/utils.ts +++ b/projects/app/src/service/core/dataset/data/utils.ts @@ -1,7 +1,11 @@ -import { PgDatasetTableName } from '@/constants/plugin'; -import { getVector } from '@/pages/api/openapi/plugin/vector'; -import { PgClient } from '@/service/pg'; -import { delay } from '@/utils/tools'; +import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant'; +import { + SearchDataResponseItemType, + SearchDataResultItemType +} from '@fastgpt/global/core/dataset/type'; +import { PgClient } from '@fastgpt/service/common/pg'; +import { getVectorsByText } from '../../ai/vector'; +import { getPgDataWithCollection } from './controller'; /** * Same value judgment @@ -27,99 +31,6 @@ export async function hasSameValue({ } } -type Props = { - userId: string; - q: string; - a?: string; - model: string; -}; - -export async function insertData2Dataset({ - userId, - datasetId, - collectionId, - q, - a = '', - model, - billId -}: Props & { - datasetId: string; - collectionId: string; - billId?: string; -}) { - if (!q || !datasetId || !collectionId || !model) { - return Promise.reject('q, datasetId, collectionId, model is required'); - } - const { vectors } = await getVector({ - model, - input: [q], - userId, - billId - }); - - let retry = 2; - async function insertPg(): Promise { - try { - const { rows } = await PgClient.insert(PgDatasetTableName, { - values: [ - [ - { key: 'vector', value: `[${vectors[0]}]` }, - { key: 'user_id', value: userId }, - { key: 'q', value: q }, - { key: 'a', value: a }, - { key: 'dataset_id', value: datasetId }, - { key: 'collection_id', value: collectionId } - ] - ] - }); - return rows[0].id; - } catch (error) { - if (--retry < 0) { - return Promise.reject(error); - } - await delay(500); - return insertPg(); - } - } - - return insertPg(); -} - -/** - * update a or a - */ -export async function updateData2Dataset({ - dataId, - userId, - q, - a = '', - model -}: Props & { dataId: string }) { - const { vectors = [] } = await (async () => { - if (q) { - return getVector({ - userId, - input: [q], - model - }); - } - return { vectors: [[]] }; - })(); - - await PgClient.update(PgDatasetTableName, { - where: [['id', dataId], 'AND', ['user_id', userId]], - values: [ - { key: 'a', value: a.replace(/'/g, '"') }, - ...(q - ? [ - { key: 'q', value: q.replace(/'/g, '"') }, - { key: 'vector', value: `[${vectors[0]}]` } - ] - : []) - ] - }); -} - /** * count one collection amount of total data */ @@ -148,18 +59,46 @@ export async function countCollectionData({ return values; } -/** - * delete data by collectionIds - */ -export async function delDataByCollectionId({ - userId, - collectionIds +export async function searchDatasetData({ + text, + model, + similarity = 0, + limit, + datasetIds = [] }: { - userId: string; - collectionIds: string[]; + text: string; + model: string; + similarity?: number; + limit: number; + datasetIds: string[]; }) { - const ids = collectionIds.map((item) => String(item)); - return PgClient.delete(PgDatasetTableName, { - where: [['user_id', userId], 'AND', `collection_id IN ('${ids.join("','")}')`] + const { vectors, tokenLen } = await getVectorsByText({ + model, + input: [text] }); + + const results: any = await PgClient.query( + `BEGIN; + SET LOCAL hnsw.ef_search = ${global.systemEnv.pgHNSWEfSearch || 100}; + select id, q, a, collection_id, (vector <#> '[${ + vectors[0] + }]') * -1 AS score from ${PgDatasetTableName} where dataset_id IN (${datasetIds + .map((id) => `'${String(id)}'`) + .join(',')}) AND vector <#> '[${vectors[0]}]' < -${similarity} order by vector <#> '[${ + vectors[0] + }]' limit ${limit}; + COMMIT;` + ); + + const rows = results?.[2]?.rows as SearchDataResultItemType[]; + const collectionsData = await getPgDataWithCollection({ pgDataList: rows }); + const searchRes: SearchDataResponseItemType[] = collectionsData.map((item, index) => ({ + ...item, + score: rows[index].score + })); + + return { + searchRes, + tokenLen + }; } diff --git a/projects/app/src/service/events/generateQA.ts b/projects/app/src/service/events/generateQA.ts index 2a6fc4a55..bad49f26b 100644 --- a/projects/app/src/service/events/generateQA.ts +++ b/projects/app/src/service/events/generateQA.ts @@ -1,17 +1,16 @@ import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; -import { pushQABill } from '@/service/common/bill/push'; +import { pushQABill } from '@/service/support/wallet/bill/push'; import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constant'; -import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode'; -import { sendInform } from '@/pages/api/user/inform/send'; -import { authBalanceByUid } from '@fastgpt/service/support/user/auth'; +import { sendOneInform } from '../support/user/inform/api'; import { getAIApi } from '@fastgpt/service/core/ai/config'; -import type { ChatCompletionRequestMessage } from '@fastgpt/global/core/ai/type.d'; -import { addLog } from '../utils/tools'; +import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d'; +import { addLog } from '@fastgpt/service/common/mongo/controller'; import { splitText2Chunks } from '@/global/common/string/tools'; import { replaceVariable } from '@/global/common/string/tools'; import { Prompt_AgentQA } from '@/global/core/prompt/agent'; import { pushDataToDatasetCollection } from '@/pages/api/core/dataset/data/pushData'; import { getErrText } from '@fastgpt/global/common/error/utils'; +import { authTeamBalance } from '../support/permission/auth/bill'; const reduceQueue = () => { global.qaQueueLen = global.qaQueueLen > 0 ? global.qaQueueLen - 1 : 0; @@ -21,45 +20,92 @@ export async function generateQA(): Promise { if (global.qaQueueLen >= global.systemEnv.qaMaxProcess) return; global.qaQueueLen++; - let trainingId = ''; - let userId = ''; + // get training data + const { + data, + text, + done = false, + error = false + } = await (async () => { + try { + const data = ( + await MongoDatasetTraining.findOneAndUpdate( + { + mode: TrainingModeEnum.qa, + lockTime: { $lte: new Date(Date.now() - 10 * 60 * 1000) } + }, + { + lockTime: new Date() + } + ).select({ + _id: 1, + userId: 1, + teamId: 1, + tmbId: 1, + datasetId: 1, + datasetCollectionId: 1, + q: 1, + model: 1, + billId: 1, + prompt: 1 + }) + )?.toJSON(); + + // task preemption + if (!data) { + return { + done: true + }; + } + return { + data, + text: data.q + }; + } catch (error) { + console.log(`Get Training Data error`, error); + return { + error: true + }; + } + })(); + + if (done) { + reduceQueue(); + global.vectorQueueLen <= 0 && console.log(`【索引】任务完成`); + return; + } + if (error || !data) { + reduceQueue(); + return generateQA(); + } + + // auth balance + try { + await authTeamBalance(data.teamId); + } catch (error) { + // send inform and lock data + try { + sendOneInform({ + type: 'system', + title: '索引生成任务中止', + content: + '由于账号余额不足,索引生成任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。', + tmbId: data.tmbId + }); + console.log('余额不足,暂停向量生成任务'); + await MongoDatasetTraining.findById(data._id, { + lockTime: new Date('2999/5/5') + }); + } catch (error) {} + reduceQueue(); + return generateQA(); + } try { - const data = await MongoDatasetTraining.findOneAndUpdate( - { - mode: TrainingModeEnum.qa, - lockTime: { $lte: new Date(Date.now() - 10 * 60 * 1000) } - }, - { - lockTime: new Date() - } - ).select({ - _id: 1, - userId: 1, - datasetCollectionId: 1, - q: 1, - model: 1, - prompt: 1, - billId: 1 - }); - - // task preemption - if (!data) { - reduceQueue(); - global.qaQueueLen <= 0 && console.log(`【QA】任务完成`); - return; - } - - trainingId = data._id; - userId = String(data.userId); - - await authBalanceByUid(userId); - const startTime = Date.now(); // request LLM to get QA - const text = data.q; - const messages: ChatCompletionRequestMessage[] = [ + const messages: ChatMessageItemType[] = [ { role: 'user', content: data.prompt @@ -84,7 +130,8 @@ export async function generateQA(): Promise { // get vector and insert await pushDataToDatasetCollection({ - userId, + teamId: data.teamId, + tmbId: data.tmbId, collectionId: data.datasetCollectionId, data: qaArr, mode: TrainingModeEnum.index, @@ -97,10 +144,11 @@ export async function generateQA(): Promise { console.log(`split result length: `, qaArr.length); console.log('生成QA成功,time:', `${(Date.now() - startTime) / 1000}s`); - // 计费 + // add bill if (qaArr.length > 0) { pushQABill({ - userId: data.userId, + teamId: data.teamId, + tmbId: data.tmbId, totalTokens, billId: data.billId }); @@ -114,36 +162,30 @@ export async function generateQA(): Promise { reduceQueue(); // log if (err?.response) { - console.log('openai error: 生成QA错误'); - console.log(err.response?.status, err.response?.statusText, err.response?.data); + addLog.info('openai error: 生成QA错误', { + status: err.response?.status, + stateusText: err.response?.statusText, + data: err.response?.data + }); } else { console.log(err); addLog.error(getErrText(err, '生成 QA 错误')); } // message error or openai account error - if (err?.message === 'invalid message format') { - await MongoDatasetTraining.findByIdAndRemove(trainingId); - } - - // 账号余额不足,删除任务 - if (userId && err === ERROR_ENUM.insufficientQuota) { - sendInform({ - type: 'system', - title: 'QA 任务中止', - content: - '由于账号余额不足,索引生成任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。', - userId + if ( + err?.message === 'invalid message format' || + err.response?.data?.error?.type === 'invalid_request_error' || + err?.code === 500 + ) { + addLog.info('invalid message format', { + text }); - console.log('余额不足,暂停向量生成任务'); - await MongoDatasetTraining.updateMany( - { - userId - }, - { - lockTime: new Date('2999/5/5') - } - ); + try { + await MongoDatasetTraining.findByIdAndUpdate(data._id, { + lockTime: new Date('2998/5/5') + }); + } catch (error) {} return generateQA(); } diff --git a/projects/app/src/service/events/generateVector.ts b/projects/app/src/service/events/generateVector.ts index 2e3b371d2..59e26af4a 100644 --- a/projects/app/src/service/events/generateVector.ts +++ b/projects/app/src/service/events/generateVector.ts @@ -1,11 +1,11 @@ -import { insertData2Dataset } from '../core/dataset/data/utils'; -import { getVector } from '@/pages/api/openapi/plugin/vector'; +import { insertData2Dataset } from '@/service/core/dataset/data/controller'; import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; -import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode'; import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constant'; -import { sendInform } from '@/pages/api/user/inform/send'; -import { addLog } from '../utils/tools'; +import { sendOneInform } from '../support/user/inform/api'; +import { addLog } from '@fastgpt/service/common/mongo/controller'; import { getErrText } from '@fastgpt/global/common/error/utils'; +import { authTeamBalance } from '@/service/support/permission/auth/bill'; +import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push'; const reduceQueue = () => { global.vectorQueueLen = global.vectorQueueLen > 0 ? global.vectorQueueLen - 1 : 0; @@ -16,68 +16,114 @@ export async function generateVector(): Promise { if (global.vectorQueueLen >= global.systemEnv.vectorMaxProcess) return; global.vectorQueueLen++; - let trainingId = ''; - let userId = ''; - let dataItems: { - q: string; - a: string; - } = { - q: '', - a: '' - }; + // get training data + const { + data, + dataItem, + done = false, + error = false + } = await (async () => { + try { + const data = ( + await MongoDatasetTraining.findOneAndUpdate( + { + mode: TrainingModeEnum.index, + lockTime: { $lte: new Date(Date.now() - 1 * 60 * 1000) } + }, + { + lockTime: new Date() + } + ).select({ + _id: 1, + userId: 1, + teamId: 1, + tmbId: 1, + datasetId: 1, + datasetCollectionId: 1, + q: 1, + a: 1, + model: 1, + billId: 1 + }) + )?.toJSON(); + + // task preemption + if (!data) { + return { + done: true + }; + } + return { + data, + dataItem: { + q: data.q.replace(/[\x00-\x08]/g, ' '), + a: data.a?.replace(/[\x00-\x08]/g, ' ') || '' + } + }; + } catch (error) { + console.log(`Get Training Data error`, error); + return { + error: true + }; + } + })(); + + if (done) { + reduceQueue(); + global.vectorQueueLen <= 0 && console.log(`【索引】任务完成`); + return; + } + if (error || !data) { + reduceQueue(); + return generateVector(); + } + + // auth balance + try { + await authTeamBalance(data.teamId); + } catch (error) { + // send inform and lock data + try { + sendOneInform({ + type: 'system', + title: '索引生成任务中止', + content: + '由于账号余额不足,索引生成任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。', + tmbId: data.tmbId + }); + console.log('余额不足,暂停向量生成任务'); + await MongoDatasetTraining.findById(data._id, { + lockTime: new Date('2999/5/5') + }); + } catch (error) {} + reduceQueue(); + return generateVector(); + } + + // create vector and insert try { - const data = await MongoDatasetTraining.findOneAndUpdate( - { - mode: TrainingModeEnum.index, - lockTime: { $lte: new Date(Date.now() - 1 * 60 * 1000) } - }, - { - lockTime: new Date() - } - ) - .select({ - _id: 1, - userId: 1, - datasetId: 1, - datasetCollectionId: 1, - q: 1, - a: 1, - model: 1, - billId: 1 - }) - .lean(); - - // task preemption - if (!data) { - reduceQueue(); - global.vectorQueueLen <= 0 && console.log(`【索引】任务完成`); - return; - } - - trainingId = data._id; - userId = String(data.userId); - - dataItems = { - q: data.q.replace(/[\x00-\x08]/g, ' '), - a: data.a?.replace(/[\x00-\x08]/g, ' ') || '' - }; - - // insert data 2 pg - await insertData2Dataset({ - userId, + // insert data to pg + const { tokenLen } = await insertData2Dataset({ + teamId: data.teamId, + tmbId: data.teamId, datasetId: data.datasetId, collectionId: data.datasetCollectionId, - q: dataItems.q, - a: dataItems.a, + q: dataItem.q, + a: dataItem.a, + model: data.model + }); + // push bill + pushGenerateVectorBill({ + teamId: data.teamId, + tmbId: data.teamId, + tokenLen: tokenLen, model: data.model, billId: data.billId }); // delete data from training await MongoDatasetTraining.findByIdAndDelete(data._id); - // console.log(`生成向量成功: ${data._id}`); - reduceQueue(); generateVector(); } catch (err: any) { @@ -97,48 +143,20 @@ export async function generateVector(): Promise { // message error or openai account error if ( err?.message === 'invalid message format' || - err.response?.data?.error?.type === 'invalid_request_error' + err.response?.data?.error?.type === 'invalid_request_error' || + err?.code === 500 ) { addLog.info('invalid message format', { - dataItems + dataItem }); try { - await MongoDatasetTraining.findByIdAndUpdate(trainingId, { + await MongoDatasetTraining.findByIdAndUpdate(data._id, { lockTime: new Date('2998/5/5') }); } catch (error) {} return generateVector(); } - // err vector data - if (err?.code === 500) { - await MongoDatasetTraining.findByIdAndDelete(trainingId); - return generateVector(); - } - - // 账号余额不足,暂停任务 - if (userId && err === ERROR_ENUM.insufficientQuota) { - try { - sendInform({ - type: 'system', - title: '索引生成任务中止', - content: - '由于账号余额不足,索引生成任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。', - userId - }); - console.log('余额不足,暂停向量生成任务'); - await MongoDatasetTraining.updateMany( - { - userId - }, - { - lockTime: new Date('2999/5/5') - } - ); - } catch (error) {} - return generateVector(); - } - setTimeout(() => { generateVector(); }, 1000); diff --git a/projects/app/src/service/events/sendInform.ts b/projects/app/src/service/events/sendInform.ts deleted file mode 100644 index d43f32737..000000000 --- a/projects/app/src/service/events/sendInform.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const startSendInform = async () => { - if (global.sendInformQueue.length === 0 || global.sendInformQueueLen > 0) return; - global.sendInformQueueLen++; - - try { - const fn = global.sendInformQueue[global.sendInformQueue.length - 1]; - await fn(); - global.sendInformQueue.pop(); - global.sendInformQueueLen--; - - startSendInform(); - } catch (error) { - global.sendInformQueueLen--; - startSendInform(); - } -}; diff --git a/projects/app/src/service/lib/gridfs.ts b/projects/app/src/service/lib/gridfs.ts deleted file mode 100644 index fbf554145..000000000 --- a/projects/app/src/service/lib/gridfs.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { Types, connectionMongo } from '@fastgpt/service/common/mongo'; -import fs from 'fs'; -import fsp from 'fs/promises'; -import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode'; -import type { GSFileInfoType } from '@/types/common/file'; - -enum BucketNameEnum { - dataset = 'dataset' -} - -export class GridFSStorage { - readonly type = 'gridfs'; - readonly bucket: `${BucketNameEnum}`; - readonly uid: string; - - constructor(bucket: `${BucketNameEnum}`, uid: string) { - this.bucket = bucket; - this.uid = String(uid); - } - Collection() { - return connectionMongo.connection.db.collection(`${this.bucket}.files`); - } - GridFSBucket() { - return new connectionMongo.mongo.GridFSBucket(connectionMongo.connection.db, { - bucketName: this.bucket - }); - } - - async save({ - path, - filename, - metadata = {} - }: { - path: string; - filename: string; - metadata?: Record; - }) { - if (!path) return Promise.reject(`filePath is empty`); - if (!filename) return Promise.reject(`filename is empty`); - - const stats = await fsp.stat(path); - if (!stats.isFile()) return Promise.reject(`${path} is not a file`); - - metadata.userId = this.uid; - // create a gridfs bucket - const bucket = this.GridFSBucket(); - - const stream = bucket.openUploadStream(filename, { - metadata, - contentType: metadata?.contentType - }); - - // save to gridfs - await new Promise((resolve, reject) => { - fs.createReadStream(path) - .pipe(stream as any) - .on('finish', resolve) - .on('error', reject); - }); - - return String(stream.id); - } - async findAndAuthFile(id: string): Promise { - if (!id) { - return Promise.reject(`id is empty`); - } - - // create a gridfs bucket - const bucket = this.GridFSBucket(); - - // check if file exists - const files = await bucket.find({ _id: new Types.ObjectId(id) }).toArray(); - const file = files.shift(); - if (!file) { - return Promise.reject(`file not found`); - } - - if (file.metadata?.userId !== this.uid) { - return Promise.reject(ERROR_ENUM.unAuthFile); - } - - return { - id: String(file._id), - filename: file.filename, - contentType: file.metadata?.contentType, - encoding: file.metadata?.encoding, - uploadDate: file.uploadDate, - size: file.length - }; - } - - async delete(id: string) { - await this.findAndAuthFile(id); - const bucket = this.GridFSBucket(); - - await bucket.delete(new Types.ObjectId(id)); - return true; - } - - async deleteFilesByDatasetId(datasetId: string) { - if (!datasetId) return; - const collection = this.Collection(); - - return collection.deleteMany({ - 'metadata.datasetId': String(datasetId) - }); - } - - async download(id: string) { - await this.findAndAuthFile(id); - - const bucket = this.GridFSBucket(); - - const stream = bucket.openDownloadStream(new Types.ObjectId(id)); - - const buf: Buffer = await new Promise((resolve, reject) => { - const buffers: Buffer[] = []; - stream.on('data', (data) => buffers.push(data)); - stream.on('error', reject); - stream.on('end', () => resolve(Buffer.concat(buffers))); - }); - - return buf; - } -} diff --git a/projects/app/src/service/models/app.ts b/projects/app/src/service/models/app.ts deleted file mode 100644 index dcd411b45..000000000 --- a/projects/app/src/service/models/app.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { connectionMongo, type Model } from '@fastgpt/service/common/mongo'; -const { Schema, model, models } = connectionMongo; -import { AppSchema as AppType } from '@/types/mongoSchema'; - -const AppSchema = new Schema({ - userId: { - type: Schema.Types.ObjectId, - ref: 'user', - required: true - }, - name: { - type: String, - required: true - }, - type: { - type: String, - default: 'advanced', - enum: ['basic', 'advanced'] - }, - avatar: { - type: String, - default: '/icon/logo.svg' - }, - intro: { - type: String, - default: '' - }, - updateTime: { - type: Date, - default: () => new Date() - }, - share: { - topNum: { - type: Number, - default: 0 - }, - isShare: { - type: Boolean, - default: false - }, - isShareDetail: { - // share model detail info. false: just show name and intro - type: Boolean, - default: false - }, - intro: { - type: String, - default: '', - maxlength: 150 - }, - collection: { - type: Number, - default: 0 - } - }, - modules: { - type: Array, - default: [] - }, - inited: { - type: Boolean - }, - // 弃 - chat: Object -}); - -try { - AppSchema.index({ updateTime: -1 }); - AppSchema.index({ 'share.collection': -1 }); -} catch (error) { - console.log(error); -} - -export const App: Model = models['app'] || model('app', AppSchema); diff --git a/projects/app/src/service/moduleDispatch/agent/classifyQuestion.ts b/projects/app/src/service/moduleDispatch/agent/classifyQuestion.ts index 09b679778..f2dda766d 100644 --- a/projects/app/src/service/moduleDispatch/agent/classifyQuestion.ts +++ b/projects/app/src/service/moduleDispatch/agent/classifyQuestion.ts @@ -1,15 +1,15 @@ import { adaptChat2GptMessages } from '@/utils/common/adapt/message'; import { ChatContextFilter } from '@/service/common/tiktoken'; -import type { moduleDispatchResType, ChatItemType } from '@/types/chat'; -import { ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat'; +import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d'; +import { ChatRoleEnum, TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; import { getAIApi } from '@fastgpt/service/core/ai/config'; -import type { ClassifyQuestionAgentItemType } from '@/types/app'; +import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/module/type.d'; import { SystemInputEnum } from '@/constants/app'; import { FlowNodeSpecialInputKeyEnum } from '@fastgpt/global/core/module/node/constant'; import type { ModuleDispatchProps } from '@/types/core/chat/type'; import { replaceVariable } from '@/global/common/string/tools'; import { Prompt_CQJson } from '@/global/core/prompt/agent'; -import { FunctionModelItemType } from '@/types/model'; +import { FunctionModelItemType } from '@fastgpt/global/core/ai/model.d'; import { getCQModel } from '@/service/core/ai/model'; type Props = ModuleDispatchProps<{ @@ -88,7 +88,7 @@ ${systemPrompt} const filterMessages = ChatContextFilter({ messages, - maxTokens: cqModel.maxToken + maxTokens: cqModel.maxContext }); const adaptMessages = adaptChat2GptMessages({ messages: filterMessages, reserveId: false }); diff --git a/projects/app/src/service/moduleDispatch/agent/extract.ts b/projects/app/src/service/moduleDispatch/agent/extract.ts index 528e2c820..36f1a8a88 100644 --- a/projects/app/src/service/moduleDispatch/agent/extract.ts +++ b/projects/app/src/service/moduleDispatch/agent/extract.ts @@ -1,14 +1,14 @@ import { adaptChat2GptMessages } from '@/utils/common/adapt/message'; import { ChatContextFilter } from '@/service/common/tiktoken'; -import type { moduleDispatchResType, ChatItemType } from '@/types/chat'; -import { ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat'; +import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d'; +import { ChatRoleEnum, TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; import { getAIApi } from '@fastgpt/service/core/ai/config'; -import type { ContextExtractAgentItemType } from '@/types/app'; +import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type'; import { ContextExtractEnum } from '@/constants/flow/flowField'; import type { ModuleDispatchProps } from '@/types/core/chat/type'; import { Prompt_ExtractJson } from '@/global/core/prompt/agent'; import { replaceVariable } from '@/global/common/string/tools'; -import { FunctionModelItemType } from '@/types/model'; +import { FunctionModelItemType } from '@fastgpt/global/core/ai/model.d'; type Props = ModuleDispatchProps<{ history?: ChatItemType[]; @@ -98,7 +98,7 @@ async function functionCall({ ]; const filterMessages = ChatContextFilter({ messages, - maxTokens: extractModel.maxToken + maxTokens: extractModel.maxContext }); const adaptMessages = adaptChat2GptMessages({ messages: filterMessages, reserveId: false }); diff --git a/projects/app/src/service/moduleDispatch/chat/oneapi.ts b/projects/app/src/service/moduleDispatch/chat/oneapi.ts index 112fef7df..023c0525a 100644 --- a/projects/app/src/service/moduleDispatch/chat/oneapi.ts +++ b/projects/app/src/service/moduleDispatch/chat/oneapi.ts @@ -1,14 +1,15 @@ import type { NextApiResponse } from 'next'; import { ChatContextFilter } from '@/service/common/tiktoken'; -import type { ChatItemType, moduleDispatchResType } from '@/types/chat'; -import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat'; +import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant'; import { textAdaptGptResponse } from '@/utils/adapt'; import { getAIApi } from '@fastgpt/service/core/ai/config'; import type { ChatCompletion, StreamChatType } from '@fastgpt/global/core/ai/type.d'; -import { TaskResponseKeyEnum } from '@/constants/chat'; -import { countModelPrice } from '@/service/common/bill/push'; -import { ChatModelItemType } from '@/types/model'; -import { postTextCensor } from '@/web/common/plusApi/censor'; +import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; +import { countModelPrice } from '@/service/support/wallet/bill/utils'; +import type { ChatModelItemType } from '@fastgpt/global/core/ai/model.d'; +import { postTextCensor } from '@/service/common/censor'; import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constant'; import type { ModuleItemType } from '@fastgpt/global/core/module/type.d'; import { countMessagesTokens, sliceMessagesTB } from '@/global/common/tiktoken'; @@ -99,7 +100,6 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise tokensLimit ? tokensLimit - promptsToken : maxToken; + maxToken = promptsToken + model.maxResponse > tokensLimit ? tokensLimit - promptsToken : maxToken; return { max_tokens: maxToken diff --git a/projects/app/src/service/moduleDispatch/dataset/search.ts b/projects/app/src/service/moduleDispatch/dataset/search.ts index 59e58401a..b642113ab 100644 --- a/projects/app/src/service/moduleDispatch/dataset/search.ts +++ b/projects/app/src/service/moduleDispatch/dataset/search.ts @@ -1,17 +1,11 @@ -import { PgClient } from '@/service/pg'; -import type { moduleDispatchResType } from '@/types/chat'; -import { TaskResponseKeyEnum } from '@/constants/chat'; -import { getVector } from '@/pages/api/openapi/plugin/vector'; -import { countModelPrice } from '@/service/common/bill/push'; +import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d'; +import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; +import { countModelPrice } from '@/service/support/wallet/bill/utils'; import type { SelectedDatasetType } from '@/types/core/dataset'; -import type { - SearchDataResponseItemType, - SearchDataResultItemType -} from '@fastgpt/global/core/dataset/type'; -import { PgDatasetTableName } from '@/constants/plugin'; +import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type'; import type { ModuleDispatchProps } from '@/types/core/chat/type'; import { ModelTypeEnum } from '@/service/core/ai/model'; -import { getDatasetDataItemInfo } from '@/pages/api/core/dataset/data/getDataById'; +import { searchDatasetData } from '@/service/core/dataset/data/utils'; type DatasetSearchProps = ModuleDispatchProps<{ datasets: SelectedDatasetType; @@ -28,7 +22,8 @@ export type KBSearchResponse = { export async function dispatchDatasetSearch(props: Record): Promise { const { - user, + teamId, + tmbId, inputs: { datasets = [], similarity = 0.4, limit = 5, userChatInput } } = props as DatasetSearchProps; @@ -42,34 +37,15 @@ export async function dispatchDatasetSearch(props: Record): Promise // get vector const vectorModel = datasets[0]?.vectorModel || global.vectorModels[0]; - const { vectors, tokenLen } = await getVector({ + + const { searchRes, tokenLen } = await searchDatasetData({ + text: userChatInput, model: vectorModel.model, - input: [userChatInput] + similarity, + limit, + datasetIds: datasets.map((item) => item.datasetId) }); - // search kb - const results: any = await PgClient.query( - `BEGIN; - SET LOCAL hnsw.ef_search = ${global.systemEnv.pgHNSWEfSearch || 100}; - select id, q, a, dataset_id, collection_id, (vector <#> '[${ - vectors[0] - }]') * -1 AS score from ${PgDatasetTableName} where user_id='${ - user._id - }' AND dataset_id IN (${datasets - .map((item) => `'${item.datasetId}'`) - .join(',')}) AND vector <#> '[${vectors[0]}]' < -${similarity} order by vector <#> '[${ - vectors[0] - }]' limit ${limit}; - COMMIT;` - ); - - const rows = results?.[2]?.rows as SearchDataResultItemType[]; - const collectionsData = await getDatasetDataItemInfo({ pgDataList: rows }); - const searchRes: SearchDataResponseItemType[] = collectionsData.map((item, index) => ({ - ...item, - score: rows[index].score - })); - return { isEmpty: searchRes.length === 0 ? true : undefined, unEmpty: searchRes.length > 0 ? true : undefined, diff --git a/projects/app/src/service/moduleDispatch/index.ts b/projects/app/src/service/moduleDispatch/index.ts index 543b5543e..996817e37 100644 --- a/projects/app/src/service/moduleDispatch/index.ts +++ b/projects/app/src/service/moduleDispatch/index.ts @@ -1,12 +1,303 @@ -export * from './init/history'; -export * from './init/userChatInput'; -export * from './chat/oneapi'; -export * from './dataset/search'; -export * from './tools/answer'; -export * from './tools/http'; -export * from './tools/runApp'; -export * from './agent/classifyQuestion'; -export * from './agent/extract'; -export * from './plugin/run'; -export * from './plugin/runInput'; -export * from './plugin/runOutput'; +import { NextApiResponse } from 'next'; +import { SystemInputEnum, SystemOutputEnum } from '@/constants/app'; +import { RunningModuleItemType } from '@/types/app'; +import { ModuleDispatchProps } from '@/types/core/chat/type'; +import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api'; +import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant'; +import { ModuleItemType } from '@fastgpt/global/core/module/type'; +import { UserType } from '@fastgpt/global/support/user/type'; +import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; +import { replaceVariable } from '@/global/common/string/tools'; +import { responseWrite } from '@fastgpt/service/common/response'; +import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant'; +import { getSystemTime } from '@fastgpt/global/common/time/timezone'; +import { initModuleType } from '@/constants/flow'; + +import { dispatchHistory } from './init/history'; +import { dispatchChatInput } from './init/userChatInput'; +import { dispatchChatCompletion } from './chat/oneapi'; +import { dispatchDatasetSearch } from './dataset/search'; +import { dispatchAnswer } from './tools/answer'; +import { dispatchClassifyQuestion } from './agent/classifyQuestion'; +import { dispatchContentExtract } from './agent/extract'; +import { dispatchHttpRequest } from './tools/http'; +import { dispatchAppRequest } from './tools/runApp'; +import { dispatchRunPlugin } from './plugin/run'; +import { dispatchPluginInput } from './plugin/runInput'; +import { dispatchPluginOutput } from './plugin/runOutput'; + +/* running */ +export async function dispatchModules({ + res, + chatId, + modules, + user, + teamId, + tmbId, + params = {}, + variables = {}, + stream = false, + detail = false +}: { + res: NextApiResponse; + chatId?: string; + modules: ModuleItemType[]; + user: UserType; + teamId: string; + tmbId: string; + params?: Record; + variables?: Record; + stream?: boolean; + detail?: boolean; +}) { + variables = { + ...getSystemVariable({ timezone: user.timezone }), + ...variables + }; + const runningModules = loadModules(modules, variables); + + // let storeData: Record = {}; // after module used + let chatResponse: ChatHistoryItemResType[] = []; // response request and save to database + let chatAnswerText = ''; // AI answer + let runningTime = Date.now(); + + function pushStore( + { inputs = [] }: RunningModuleItemType, + { + answerText = '', + responseData + }: { + answerText?: string; + responseData?: ChatHistoryItemResType | ChatHistoryItemResType[]; + } + ) { + const time = Date.now(); + if (responseData) { + if (Array.isArray(responseData)) { + chatResponse = chatResponse.concat(responseData); + } else { + chatResponse.push({ + ...responseData, + runningTime: +((time - runningTime) / 1000).toFixed(2) + }); + } + } + runningTime = time; + + const isResponseAnswerText = + inputs.find((item) => item.key === SystemInputEnum.isResponseAnswerText)?.value ?? true; + if (isResponseAnswerText) { + chatAnswerText += answerText; + } + } + function moduleInput( + module: RunningModuleItemType, + data: Record = {} + ): Promise { + const checkInputFinish = () => { + return !module.inputs.find((item: any) => item.value === undefined); + }; + const updateInputValue = (key: string, value: any) => { + const index = module.inputs.findIndex((item: any) => item.key === key); + if (index === -1) return; + module.inputs[index].value = value; + }; + + const set = new Set(); + + return Promise.all( + Object.entries(data).map(([key, val]: any) => { + updateInputValue(key, val); + + if (!set.has(module.moduleId) && checkInputFinish()) { + set.add(module.moduleId); + // remove switch + updateInputValue(SystemInputEnum.switch, undefined); + return moduleRun(module); + } + }) + ); + } + function moduleOutput( + module: RunningModuleItemType, + result: Record = {} + ): Promise { + pushStore(module, result); + return Promise.all( + module.outputs.map((outputItem) => { + if (result[outputItem.key] === undefined) return; + /* update output value */ + outputItem.value = result[outputItem.key]; + + /* update target */ + return Promise.all( + outputItem.targets.map((target: any) => { + // find module + const targetModule = runningModules.find((item) => item.moduleId === target.moduleId); + if (!targetModule) return; + + return moduleInput(targetModule, { [target.key]: outputItem.value }); + }) + ); + }) + ); + } + async function moduleRun(module: RunningModuleItemType): Promise { + if (res.closed) return Promise.resolve(); + + if (stream && detail && module.showStatus) { + responseStatus({ + res, + name: module.name, + status: 'running' + }); + } + + // get fetch params + const params: Record = {}; + module.inputs.forEach((item: any) => { + params[item.key] = item.value; + }); + const props: ModuleDispatchProps> = { + res, + stream, + detail, + variables, + outputs: module.outputs, + user, + teamId, + tmbId, + inputs: params + }; + + const dispatchRes: Record = await (async () => { + const callbackMap: Record = { + [FlowNodeTypeEnum.historyNode]: dispatchHistory, + [FlowNodeTypeEnum.questionInput]: dispatchChatInput, + [FlowNodeTypeEnum.answerNode]: dispatchAnswer, + [FlowNodeTypeEnum.chatNode]: dispatchChatCompletion, + [FlowNodeTypeEnum.datasetSearchNode]: dispatchDatasetSearch, + [FlowNodeTypeEnum.classifyQuestion]: dispatchClassifyQuestion, + [FlowNodeTypeEnum.contentExtract]: dispatchContentExtract, + [FlowNodeTypeEnum.httpRequest]: dispatchHttpRequest, + [FlowNodeTypeEnum.runApp]: dispatchAppRequest, + [FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin, + [FlowNodeTypeEnum.pluginInput]: dispatchPluginInput, + [FlowNodeTypeEnum.pluginOutput]: dispatchPluginOutput + }; + if (callbackMap[module.flowType]) { + return callbackMap[module.flowType](props); + } + return {}; + })(); + + const formatResponseData = (() => { + if (!dispatchRes[TaskResponseKeyEnum.responseData]) return undefined; + if (Array.isArray(dispatchRes[TaskResponseKeyEnum.responseData])) + return dispatchRes[TaskResponseKeyEnum.responseData]; + return { + ...dispatchRes[TaskResponseKeyEnum.responseData], + moduleName: module.name, + moduleType: module.flowType + }; + })(); + + return moduleOutput(module, { + [SystemOutputEnum.finish]: true, + ...dispatchRes, + [TaskResponseKeyEnum.responseData]: formatResponseData + }); + } + + // start process width initInput + const initModules = runningModules.filter((item) => initModuleType[item.flowType]); + + await Promise.all(initModules.map((module) => moduleInput(module, params))); + + // focus running pluginOutput + const pluginOutputModule = runningModules.find( + (item) => item.flowType === FlowNodeTypeEnum.pluginOutput + ); + if (pluginOutputModule) { + await moduleRun(pluginOutputModule); + } + + return { + [TaskResponseKeyEnum.answerText]: chatAnswerText, + [TaskResponseKeyEnum.responseData]: chatResponse + }; +} + +/* init store modules to running modules */ +function loadModules( + modules: ModuleItemType[], + variables: Record +): RunningModuleItemType[] { + return modules.map((module) => { + return { + moduleId: module.moduleId, + name: module.name, + flowType: module.flowType, + showStatus: module.showStatus, + inputs: module.inputs + .filter((item) => item.connected) // filter unconnected target input + .map((item) => { + if (typeof item.value !== 'string') { + return { + key: item.key, + value: item.value + }; + } + + // variables replace + const replacedVal = replaceVariable(item.value, variables); + + return { + key: item.key, + value: replacedVal + }; + }), + outputs: module.outputs + .map((item) => ({ + key: item.key, + answer: item.key === TaskResponseKeyEnum.answerText, + value: undefined, + targets: item.targets + })) + .sort((a, b) => { + // finish output always at last + if (a.key === SystemOutputEnum.finish) return 1; + if (b.key === SystemOutputEnum.finish) return -1; + return 0; + }) + }; + }); +} + +/* sse response modules staus */ +export function responseStatus({ + res, + status, + name +}: { + res: NextApiResponse; + status?: 'running' | 'finish'; + name?: string; +}) { + if (!name) return; + responseWrite({ + res, + event: sseResponseEventEnum.moduleStatus, + data: JSON.stringify({ + status: 'running', + name + }) + }); +} + +/* get system variable */ +export function getSystemVariable({ timezone }: { timezone: string }) { + return { + cTime: getSystemTime(timezone) + }; +} diff --git a/projects/app/src/service/moduleDispatch/init/history.tsx b/projects/app/src/service/moduleDispatch/init/history.tsx index c49196f40..b1cdf312d 100644 --- a/projects/app/src/service/moduleDispatch/init/history.tsx +++ b/projects/app/src/service/moduleDispatch/init/history.tsx @@ -1,5 +1,5 @@ import { SystemInputEnum } from '@/constants/app'; -import { ChatItemType } from '@/types/chat'; +import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { ModuleDispatchProps } from '@/types/core/chat/type'; export type HistoryProps = ModuleDispatchProps<{ maxContext: number; diff --git a/projects/app/src/service/moduleDispatch/plugin/run.ts b/projects/app/src/service/moduleDispatch/plugin/run.ts index 0ef3ddcbe..020cd2f92 100644 --- a/projects/app/src/service/moduleDispatch/plugin/run.ts +++ b/projects/app/src/service/moduleDispatch/plugin/run.ts @@ -1,12 +1,12 @@ import type { ModuleDispatchProps } from '@/types/core/chat/type'; -import { dispatchModules } from '@/pages/api/v1/chat/completions'; +import { dispatchModules } from '../index'; import { FlowNodeSpecialInputKeyEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant'; -import { getOnePluginDetail } from '@fastgpt/service/core/plugin/controller'; -import { TaskResponseKeyEnum } from '@/constants/chat'; -import { moduleDispatchResType } from '@/types/chat'; +import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d'; +import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; +import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; type RunPluginProps = ModuleDispatchProps<{ [FlowNodeSpecialInputKeyEnum.pluginId]: string; @@ -19,11 +19,6 @@ type RunPluginResponse = { export const dispatchRunPlugin = async (props: RunPluginProps): Promise => { const { - res, - variables, - user, - stream, - detail, inputs: { pluginId, ...data } } = props; @@ -31,19 +26,15 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise item.moduleType === FlowNodeTypeEnum.pluginOutput); diff --git a/projects/app/src/service/moduleDispatch/plugin/runOutput.ts b/projects/app/src/service/moduleDispatch/plugin/runOutput.ts index 14aa12d74..47cb992c3 100644 --- a/projects/app/src/service/moduleDispatch/plugin/runOutput.ts +++ b/projects/app/src/service/moduleDispatch/plugin/runOutput.ts @@ -1,5 +1,5 @@ -import { TaskResponseKeyEnum } from '@/constants/chat'; -import { moduleDispatchResType } from '@/types/chat'; +import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d'; +import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; import type { ModuleDispatchProps } from '@/types/core/chat/type'; export type PluginOutputProps = ModuleDispatchProps<{ diff --git a/projects/app/src/service/moduleDispatch/tools/answer.ts b/projects/app/src/service/moduleDispatch/tools/answer.ts index c8c485443..a77d7b72d 100644 --- a/projects/app/src/service/moduleDispatch/tools/answer.ts +++ b/projects/app/src/service/moduleDispatch/tools/answer.ts @@ -1,4 +1,5 @@ -import { sseResponseEventEnum, TaskResponseKeyEnum } from '@/constants/chat'; +import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; +import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant'; import { responseWrite } from '@fastgpt/service/common/response'; import { textAdaptGptResponse } from '@/utils/adapt'; import type { ModuleDispatchProps } from '@/types/core/chat/type'; diff --git a/projects/app/src/service/moduleDispatch/tools/http.ts b/projects/app/src/service/moduleDispatch/tools/http.ts index 54dd0db7c..1c0f58375 100644 --- a/projects/app/src/service/moduleDispatch/tools/http.ts +++ b/projects/app/src/service/moduleDispatch/tools/http.ts @@ -1,6 +1,6 @@ -import { TaskResponseKeyEnum } from '@/constants/chat'; import { HttpPropsEnum } from '@/constants/flow/flowField'; -import { moduleDispatchResType } from '@/types/chat'; +import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d'; +import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; import type { ModuleDispatchProps } from '@/types/core/chat/type'; export type HttpRequestProps = ModuleDispatchProps<{ [HttpPropsEnum.url]: string; @@ -14,13 +14,15 @@ export type HttpResponse = { export const dispatchHttpRequest = async (props: Record): Promise => { const { + chatId, variables, inputs: { url, ...body } } = props as HttpRequestProps; const requestBody = { - variables, - ...body + ...body, + chatId, + variables }; try { diff --git a/projects/app/src/service/moduleDispatch/tools/runApp.ts b/projects/app/src/service/moduleDispatch/tools/runApp.ts index 61f5d3ecf..3a636e31b 100644 --- a/projects/app/src/service/moduleDispatch/tools/runApp.ts +++ b/projects/app/src/service/moduleDispatch/tools/runApp.ts @@ -1,10 +1,11 @@ -import { moduleDispatchResType, ChatItemType } from '@/types/chat'; +import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { ModuleDispatchProps } from '@/types/core/chat/type'; import { SelectAppItemType } from '@fastgpt/global/core/module/type'; -import { dispatchModules } from '@/pages/api/v1/chat/completions'; -import { App } from '@/service/mongo'; +import { dispatchModules } from '../index'; +import { MongoApp } from '@fastgpt/service/core/app/schema'; import { responseWrite } from '@fastgpt/service/common/response'; -import { ChatRoleEnum, TaskResponseKeyEnum, sseResponseEventEnum } from '@/constants/chat'; +import { ChatRoleEnum, TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; +import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant'; import { textAdaptGptResponse } from '@/utils/adapt'; type Props = ModuleDispatchProps<{ @@ -18,21 +19,20 @@ type Response = { [TaskResponseKeyEnum.history]: ChatItemType[]; }; -export const dispatchAppRequest = async (props: Record): Promise => { +export const dispatchAppRequest = async (props: Props): Promise => { const { res, - variables, user, stream, detail, inputs: { userChatInput, history = [], app } - } = props as Props; + } = props; if (!userChatInput) { return Promise.reject('Input is empty'); } - const appData = await App.findOne({ + const appData = await MongoApp.findOne({ _id: app.id, userId: user._id }); @@ -52,16 +52,12 @@ export const dispatchAppRequest = async (props: Record): Promise { - await connectMongo({ +export function connectToDatabase(): Promise { + return connectMongo({ beforeHook: () => { initGlobal(); getInitConfig(); }, - afterHook: async () => { - await initRootUser(); + afterHook: () => { initPg(); // start queue startQueue(); + return initRootUser(); } }); } @@ -31,6 +33,9 @@ async function initRootUser() { }); const psw = process.env.DEFAULT_ROOT_PSW || '123456'; + let rootId = rootUser?._id || ''; + + // init root user if (rootUser) { await MongoUser.findOneAndUpdate( { username: 'root' }, @@ -40,12 +45,15 @@ async function initRootUser() { } ); } else { - await MongoUser.create({ + const { _id } = await MongoUser.create({ username: 'root', password: hashStr(psw), balance: 999999 * PRICE_SCALE }); + rootId = _id; } + // init root team + await createDefaultTeam({ userId: rootId, maxSize: 1 }); console.log(`root user init:`, { username: 'root', @@ -53,10 +61,6 @@ async function initRootUser() { }); } catch (error) { console.log('init root user error', error); + exit(1); } } - -export * from './models/chat'; -export * from './models/chatItem'; -export * from './models/app'; -export * from './common/bill/schema'; diff --git a/projects/app/src/service/response.ts b/projects/app/src/service/response.ts deleted file mode 100644 index b26505cc8..000000000 --- a/projects/app/src/service/response.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { sseResponseEventEnum } from '@/constants/chat'; -import { NextApiResponse } from 'next'; -import { proxyError, ERROR_RESPONSE, ERROR_ENUM } from '@fastgpt/global/common/error/errorCode'; -import { addLog } from './utils/tools'; -import { clearCookie } from '@fastgpt/service/support/user/auth'; -import { responseWrite } from '@fastgpt/service/common/response'; - -export interface ResponseType { - code: number; - message: string; - data: T; -} - -export const jsonRes = ( - res: NextApiResponse, - props?: { - code?: number; - message?: string; - data?: T; - error?: any; - } -) => { - const { code = 200, message = '', data = null, error } = props || {}; - - const errResponseKey = typeof error === 'string' ? error : error?.message; - // Specified error - if (ERROR_RESPONSE[errResponseKey]) { - // login is expired - if (errResponseKey === ERROR_ENUM.unAuthorization) { - clearCookie(res); - } - - return res.json(ERROR_RESPONSE[errResponseKey]); - } - - // another error - let msg = ''; - if ((code < 200 || code >= 400) && !message) { - msg = error?.response?.statusText || error?.message || '请求错误'; - if (typeof error === 'string') { - msg = error; - } else if (proxyError[error?.code]) { - msg = '网络连接异常'; - } else if (error?.response?.data?.error?.message) { - msg = error?.response?.data?.error?.message; - } else if (error?.error?.message) { - msg = error?.error?.message; - } - - addLog.error(`response error: ${msg}`, error); - } - - res.status(code).json({ - code, - statusText: '', - message: message || msg, - data: data !== undefined ? data : null - }); -}; - -export const sseErrRes = (res: NextApiResponse, error: any) => { - const errResponseKey = typeof error === 'string' ? error : error?.message; - - // Specified error - if (ERROR_RESPONSE[errResponseKey]) { - // login is expired - if (errResponseKey === ERROR_ENUM.unAuthorization) { - clearCookie(res); - } - - return responseWrite({ - res, - event: sseResponseEventEnum.error, - data: JSON.stringify(ERROR_RESPONSE[errResponseKey]) - }); - } - - let msg = error?.response?.statusText || error?.message || '请求错误'; - if (typeof error === 'string') { - msg = error; - } else if (proxyError[error?.code]) { - msg = '网络连接异常'; - } else if (error?.response?.data?.error?.message) { - msg = error?.response?.data?.error?.message; - } else if (error?.error?.message) { - msg = error?.error?.message; - } - - addLog.error(`sse error: ${msg}`, error); - - responseWrite({ - res, - event: sseResponseEventEnum.error, - data: JSON.stringify({ message: msg }) - }); -}; diff --git a/projects/app/src/service/support/outLink/auth.ts b/projects/app/src/service/support/outLink/auth.ts new file mode 100644 index 000000000..4a0a9c123 --- /dev/null +++ b/projects/app/src/service/support/outLink/auth.ts @@ -0,0 +1,14 @@ +import { POST } from '@fastgpt/service/common/api/plusRequest'; +import type { + AuthLinkLimitProps, + AuthShareChatInitProps +} from '@fastgpt/global/support/outLink/api.d'; + +export function authOutLinkLimit(data: AuthLinkLimitProps) { + return POST('/support/outLink/authLimit', data); +} + +export function authShareChatInit(data: AuthShareChatInitProps) { + if (!global.feConfigs?.isPlus) return; + return POST('/support/outLink/authShareChatInit', data); +} diff --git a/projects/app/src/service/support/permission/auth/bill.ts b/projects/app/src/service/support/permission/auth/bill.ts new file mode 100644 index 000000000..4f8dd2658 --- /dev/null +++ b/projects/app/src/service/support/permission/auth/bill.ts @@ -0,0 +1,8 @@ +import { GET } from '@fastgpt/service/common/api/plusRequest'; + +export const authTeamBalance = async (teamId: string) => { + if (global.systemEnv.pluginBaseUrl) { + return GET('/support/permission/authBalance', { teamId }); + } + return true; +}; diff --git a/projects/app/src/service/support/permission/auth/dataset.ts b/projects/app/src/service/support/permission/auth/dataset.ts new file mode 100644 index 000000000..dc167ae49 --- /dev/null +++ b/projects/app/src/service/support/permission/auth/dataset.ts @@ -0,0 +1,36 @@ +import { getDatasetPgData } from '@/service/core/dataset/data/controller'; +import { PgDataItemType } from '@fastgpt/global/core/dataset/type'; +import { AuthResponseType } from '@fastgpt/global/support/permission/type'; +import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset'; +import { parseHeaderCert } from '@fastgpt/service/support/permission/controller'; +import { AuthModeType } from '@fastgpt/service/support/permission/type'; + +export async function authDatasetData({ + dataId, + ...props +}: AuthModeType & { + dataId: string; +}): Promise< + AuthResponseType & { + datasetData: PgDataItemType; + } +> { + const result = await parseHeaderCert(props); + const { tmbId } = result; + // get pg data + const datasetData = await getDatasetPgData({ id: dataId }); + + const isOwner = String(datasetData.tmbId) === tmbId; + // data has the same permissions as collection + const { canWrite } = await authDatasetCollection({ + ...props, + collectionId: datasetData.collectionId + }); + + return { + ...result, + datasetData, + isOwner, + canWrite + }; +} diff --git a/projects/app/src/service/support/permission/auth/outLink.ts b/projects/app/src/service/support/permission/auth/outLink.ts new file mode 100644 index 000000000..3559e6fe3 --- /dev/null +++ b/projects/app/src/service/support/permission/auth/outLink.ts @@ -0,0 +1,31 @@ +import { authOutLinkLimit } from '@/service/support/outLink/auth'; +import { AuthLinkChatProps } from '@fastgpt/global/support/outLink/api.d'; +import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { getUserAndAuthBalance } from './user'; +import { authOutLinkValid } from '@fastgpt/service/support/permission/auth/outLink'; + +export async function authOutLinkChat({ + shareId, + ip, + authToken, + question +}: AuthLinkChatProps & { + shareId: string; +}) { + // get outLink + const { shareChat, app } = await authOutLinkValid({ shareId }); + + const [user] = await Promise.all([ + getUserAndAuthBalance({ tmbId: shareChat.tmbId, minBalance: 0 }), + global.feConfigs?.isPlus + ? authOutLinkLimit({ outLink: shareChat, ip, authToken, question }) + : undefined + ]); + + return { + authType: AuthUserTypeEnum.token, + responseDetail: shareChat.responseDetail, + user, + app + }; +} diff --git a/projects/app/src/service/support/permission/auth/user.ts b/projects/app/src/service/support/permission/auth/user.ts new file mode 100644 index 000000000..819cfb5c8 --- /dev/null +++ b/projects/app/src/service/support/permission/auth/user.ts @@ -0,0 +1,48 @@ +import { AuthResponseType } from '@fastgpt/global/support/permission/type'; +import { parseHeaderCert } from '@fastgpt/service/support/permission/controller'; +import { AuthModeType } from '@fastgpt/service/support/permission/type'; +import { UserErrEnum } from '@fastgpt/global/common/error/code/user'; +import { UserType } from '@fastgpt/global/support/user/type'; +import { getUserDetail } from '@/service/support/user/controller'; + +export async function getUserAndAuthBalance({ + tmbId, + minBalance +}: { + tmbId: string; + minBalance?: number; +}) { + const user = await getUserDetail({ tmbId }); + + if (!user) { + return Promise.reject(UserErrEnum.unAuthUser); + } + if (minBalance !== undefined && global.feConfigs.isPlus && user.team.balance < minBalance) { + return Promise.reject(UserErrEnum.balanceNotEnough); + } + + return user; +} + +/* get user */ +export async function authUser({ + minBalance, + ...props +}: AuthModeType & { + minBalance?: number; +}): Promise< + AuthResponseType & { + user: UserType; + } +> { + const { userId, teamId, tmbId } = await parseHeaderCert(props); + + return { + userId, + teamId, + tmbId, + user: await getUserAndAuthBalance({ tmbId, minBalance }), + isOwner: true, + canWrite: true + }; +} diff --git a/projects/app/src/service/support/user/controller.ts b/projects/app/src/service/support/user/controller.ts new file mode 100644 index 000000000..ba9abb4c9 --- /dev/null +++ b/projects/app/src/service/support/user/controller.ts @@ -0,0 +1,30 @@ +import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode'; +import { MongoUser } from '@fastgpt/service/support/user/schema'; +import { UserType } from '@fastgpt/global/support/user/type'; +import { getTeamInfoByTmbId } from '@fastgpt/service/support/user/team/controller'; + +export async function getUserDetail({ + tmbId, + userId +}: { + tmbId?: string; + userId?: string; +}): Promise { + const team = await getTeamInfoByTmbId({ tmbId, userId }); + const user = await MongoUser.findById(team.userId); + + if (!user) { + return Promise.reject(ERROR_ENUM.unAuthorization); + } + + return { + _id: user._id, + username: user.username, + avatar: user.avatar, + balance: user.balance, + timezone: user.timezone, + promotionRate: user.promotionRate, + openaiAccount: user.openaiAccount, + team + }; +} diff --git a/projects/app/src/service/support/user/inform/api.ts b/projects/app/src/service/support/user/inform/api.ts new file mode 100644 index 000000000..a18bf3390 --- /dev/null +++ b/projects/app/src/service/support/user/inform/api.ts @@ -0,0 +1,7 @@ +import { POST } from '@fastgpt/service/common/api/plusRequest'; +import { SendInformProps } from '@fastgpt/global/support/user/inform/type'; + +export function sendOneInform(data: SendInformProps) { + if (!global.systemEnv.pluginBaseUrl) return; + return POST('/support/user/inform/create', data); +} diff --git a/projects/app/src/service/common/bill/push.ts b/projects/app/src/service/support/wallet/bill/push.ts similarity index 52% rename from projects/app/src/service/common/bill/push.ts rename to projects/app/src/service/support/wallet/bill/push.ts index 6132db51c..4fc8d8888 100644 --- a/projects/app/src/service/common/bill/push.ts +++ b/projects/app/src/service/support/wallet/bill/push.ts @@ -1,80 +1,41 @@ -import { Bill } from '@/service/mongo'; -import { MongoUser } from '@fastgpt/service/support/user/schema'; -import { BillSourceEnum } from '@/constants/user'; -import { getModelMap, ModelTypeEnum } from '@/service/core/ai/model'; -import { ChatHistoryItemResType } from '@/types/chat'; -import { formatPrice } from '@fastgpt/global/common/bill/tools'; -import { addLog } from '@/service/utils/tools'; -import type { CreateBillType } from '@/types/common/bill'; -import { defaultQGModels } from '@/constants/model'; +import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants'; +import { getAudioSpeechModel, getModelMap, ModelTypeEnum } from '@/service/core/ai/model'; +import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d'; +import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools'; +import { addLog } from '@fastgpt/service/common/mongo/controller'; +import type { ConcatBillProps, CreateBillProps } from '@fastgpt/global/support/wallet/bill/api.d'; +import { defaultQGModels } from '@fastgpt/global/core/ai/model'; +import { POST } from '@fastgpt/service/common/api/plusRequest'; -async function createBill(data: CreateBillType) { - try { - await Promise.all([ - MongoUser.findByIdAndUpdate(data.userId, { - $inc: { balance: -data.total } - }), - Bill.create(data) - ]); - } catch (error) { - addLog.error(`createBill error`, error); - } +export function createBill(data: CreateBillProps) { + if (!global.systemEnv.pluginBaseUrl) return; + POST('/support/wallet/bill/createBill', data); } -async function concatBill({ - billId, - total, - listIndex, - tokens = 0, - userId -}: { - billId?: string; - total: number; - listIndex?: number; - tokens?: number; - userId: string; -}) { - if (!billId) return; - try { - await Promise.all([ - Bill.findOneAndUpdate( - { - _id: billId, - userId - }, - { - $inc: { - total, - ...(listIndex !== undefined && { - [`list.${listIndex}.amount`]: total, - [`list.${listIndex}.tokenLen`]: tokens - }) - } - } - ), - MongoUser.findByIdAndUpdate(userId, { - $inc: { balance: -total } - }) - ]); - } catch (error) {} +export function concatBill(data: ConcatBillProps) { + if (!global.systemEnv.pluginBaseUrl) return; + POST('/support/wallet/bill/concatBill', data); } export const pushChatBill = ({ appName, appId, - userId, + teamId, + tmbId, source, response }: { appName: string; appId: string; - userId: string; + teamId: string; + tmbId: string; source: `${BillSourceEnum}`; response: ChatHistoryItemResType[]; }) => { const total = response.reduce((sum, item) => sum + item.price, 0); createBill({ - userId, + teamId, + tmbId, appName, appId, total, @@ -88,31 +49,35 @@ export const pushChatBill = ({ }); addLog.info(`finish completions`, { source, - userId, + teamId, + tmbId, price: formatPrice(total) }); return { total }; }; export const pushQABill = async ({ - userId, + teamId, + tmbId, totalTokens, billId }: { - userId: string; + teamId: string; + tmbId: string; totalTokens: number; billId: string; }) => { addLog.info('splitData generate success', { totalTokens }); - // 获取模型单价格, 都是用 gpt35 拆分 + // 获取模型单价格 const unitPrice = global.qaModels?.[0]?.price || 3; // 计算价格 const total = unitPrice * totalTokens; concatBill({ billId, - userId, + teamId, + tmbId, total, tokens: totalTokens, listIndex: 1 @@ -123,14 +88,18 @@ export const pushQABill = async ({ export const pushGenerateVectorBill = async ({ billId, - userId, + teamId, + tmbId, tokenLen, - model + model, + source = BillSourceEnum.fastgpt }: { billId?: string; - userId: string; + teamId: string; + tmbId: string; tokenLen: number; model: string; + source?: `${BillSourceEnum}`; }) => { // 计算价格. 至少为1 const vectorModel = @@ -142,7 +111,8 @@ export const pushGenerateVectorBill = async ({ // 插入 Bill 记录 if (billId) { concatBill({ - userId, + teamId, + tmbId, total, billId, tokens: tokenLen, @@ -150,10 +120,11 @@ export const pushGenerateVectorBill = async ({ }); } else { createBill({ - userId, + teamId, + tmbId, appName: '索引生成', total, - source: BillSourceEnum.fastgpt, + source, list: [ { moduleName: '索引生成', @@ -167,25 +138,20 @@ export const pushGenerateVectorBill = async ({ return { total }; }; -export const countModelPrice = ({ - model, +export const pushQuestionGuideBill = ({ tokens, - type + teamId, + tmbId }: { - model: string; tokens: number; - type: `${ModelTypeEnum}`; + teamId: string; + tmbId: string; }) => { - const modelData = getModelMap?.[type]?.(model); - if (!modelData) return 0; - return modelData.price * tokens; -}; - -export const pushQuestionGuideBill = ({ tokens, userId }: { tokens: number; userId: string }) => { const qgModel = global.qgModels?.[0] || defaultQGModels[0]; const total = qgModel.price * tokens; createBill({ - userId, + teamId, + tmbId, appName: '下一步指引', total, source: BillSourceEnum.fastgpt, @@ -199,3 +165,37 @@ export const pushQuestionGuideBill = ({ tokens, userId }: { tokens: number; user ] }); }; + +export function pushAudioSpeechBill({ + appName = 'wallet.bill.Audio Speech', + model, + textLength, + teamId, + tmbId, + source = BillSourceEnum.fastgpt +}: { + appName?: string; + model: string; + textLength: number; + teamId: string; + tmbId: string; + source: `${BillSourceEnum}`; +}) { + const modelData = getAudioSpeechModel(model); + const total = modelData.price * textLength; + createBill({ + teamId, + tmbId, + appName, + total, + source, + list: [ + { + moduleName: appName, + amount: total, + model: modelData.name, + tokenLen: textLength + } + ] + }); +} diff --git a/projects/app/src/service/support/wallet/bill/utils.ts b/projects/app/src/service/support/wallet/bill/utils.ts new file mode 100644 index 000000000..2e5408d6a --- /dev/null +++ b/projects/app/src/service/support/wallet/bill/utils.ts @@ -0,0 +1,32 @@ +import { ModelTypeEnum, getModelMap } from '@/service/core/ai/model'; +import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants'; + +export function authType2BillSource({ + authType, + shareId, + source +}: { + authType?: `${AuthUserTypeEnum}`; + shareId?: string; + source?: `${BillSourceEnum}`; +}) { + if (source) return source; + if (shareId) return BillSourceEnum.shareLink; + if (authType === AuthUserTypeEnum.apikey) return BillSourceEnum.api; + return BillSourceEnum.fastgpt; +} + +export const countModelPrice = ({ + model, + tokens, + type +}: { + model: string; + tokens: number; + type: `${ModelTypeEnum}`; +}) => { + const modelData = getModelMap?.[type]?.(model); + if (!modelData) return 0; + return modelData.price * tokens; +}; diff --git a/projects/app/src/service/utils/auth.ts b/projects/app/src/service/utils/auth.ts deleted file mode 100644 index f72198046..000000000 --- a/projects/app/src/service/utils/auth.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { App } from '../mongo'; -import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; -import type { AppSchema } from '@/types/mongoSchema'; -import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode'; - -// 模型使用权校验 -export const authApp = async ({ - appId, - userId, - authUser = true, - authOwner = true -}: { - appId: string; - userId: string; - authUser?: boolean; - authOwner?: boolean; -}) => { - // 获取 app 数据 - const app = await App.findById(appId); - if (!app) { - return Promise.reject('App is not exists'); - } - - /* - Access verification - 1. authOwner=true or authUser = true , just owner can use - 2. authUser = false and share, anyone can use - */ - if (authOwner || authUser) { - if (userId !== String(app.userId)) return Promise.reject(ERROR_ENUM.unAuthModel); - } - - return { - app, - showModelDetail: userId === String(app.userId) - }; -}; - -// 知识库操作权限 -export const authDataset = async ({ datasetId, userId }: { datasetId: string; userId: string }) => { - const dataset = await MongoDataset.findOne({ - _id: datasetId, - userId - }); - if (dataset) { - return dataset; - } - return Promise.reject(ERROR_ENUM.unAuthDataset); -}; diff --git a/projects/app/src/service/utils/chat/saveChat.ts b/projects/app/src/service/utils/chat/saveChat.ts index 718696fd9..99bdfb4e3 100644 --- a/projects/app/src/service/utils/chat/saveChat.ts +++ b/projects/app/src/service/utils/chat/saveChat.ts @@ -1,11 +1,15 @@ -import { ChatItemType } from '@/types/chat'; -import { Chat, App, ChatItem } from '@/service/mongo'; -import { ChatSourceEnum } from '@/constants/chat'; +import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; +import { MongoApp } from '@fastgpt/service/core/app/schema'; +import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; +import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema'; +import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; +import { addLog } from '@fastgpt/service/common/mongo/controller'; type Props = { chatId: string; appId: string; - userId: string; + teamId: string; + tmbId: string; variables?: Record; isOwner: boolean; source: `${ChatSourceEnum}`; @@ -16,7 +20,8 @@ type Props = { export async function saveChat({ chatId, appId, - userId, + teamId, + tmbId, variables, isOwner, source, @@ -24,20 +29,22 @@ export async function saveChat({ content }: Props) { try { - const chatHistory = await Chat.findOne( + const chatHistory = await MongoChat.findOne( { chatId, - userId, + teamId, + tmbId, appId }, '_id' ); const promise: any[] = [ - ChatItem.insertMany( + MongoChatItem.insertMany( content.map((item) => ({ chatId, - userId, + teamId, + tmbId, appId, ...item })) @@ -46,8 +53,8 @@ export async function saveChat({ if (chatHistory) { promise.push( - Chat.updateOne( - { chatId, userId, appId }, + MongoChat.updateOne( + { chatId }, { title: content[0].value.slice(0, 20), updateTime: new Date() @@ -56,9 +63,10 @@ export async function saveChat({ ); } else { promise.push( - Chat.create({ + MongoChat.create({ chatId, - userId, + teamId, + tmbId, appId, variables, title: content[0].value.slice(0, 20), @@ -70,7 +78,7 @@ export async function saveChat({ if (isOwner && source === ChatSourceEnum.online) { promise.push( - App.findByIdAndUpdate(appId, { + MongoApp.findByIdAndUpdate(appId, { updateTime: new Date() }) ); @@ -78,16 +86,6 @@ export async function saveChat({ await Promise.all(promise); } catch (error) { - Chat.updateOne( - { chatId, userId }, - { - $push: { - content: { - $each: [], - $slice: -10 - } - } - } - ); + addLog.error(`update chat history error`, error); } } diff --git a/projects/app/src/service/utils/tools.ts b/projects/app/src/service/utils/tools.ts index cfbaa3f05..b51118419 100644 --- a/projects/app/src/service/utils/tools.ts +++ b/projects/app/src/service/utils/tools.ts @@ -19,30 +19,3 @@ export const startQueue = (limit?: number) => { generateVector(); } }; - -/* add logger */ -export const addLog = { - info: (msg: string, obj?: Record) => { - global.logger?.info(msg, { meta: obj }); - }, - error: (msg: string, error?: any) => { - global.logger?.error(msg, { - meta: { - stack: error?.stack, - ...(error?.config && { - config: { - headers: error.config.headers, - url: error.config.url, - data: error.config.data - } - }), - ...(error?.response && { - response: { - status: error.response.status, - statusText: error.response.statusText - } - }) - } - }); - } -}; diff --git a/projects/app/src/types/app.d.ts b/projects/app/src/types/app.d.ts index ac6e61bf9..9a4ff9039 100644 --- a/projects/app/src/types/app.d.ts +++ b/projects/app/src/types/app.d.ts @@ -2,40 +2,20 @@ import { FlowNodeTypeEnum, FlowNodeValTypeEnum } from '@fastgpt/global/core/modu import { XYPosition } from 'reactflow'; import { AppModuleItemTypeEnum, - AppTypeEnum, ModulesInputItemTypeEnum, VariableInputEnum } from '../constants/app'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import type { FlowNodeInputItemType, FlowNodeOutputItemType, FlowNodeOutputTargetItemType } from '@fastgpt/global/core/module/node/type.d'; import type { FlowModuleTemplateType, ModuleItemType } from '@fastgpt/global/core/module/type.d'; -import type { AppSchema, ChatSchema } from './mongoSchema'; +import type { ChatSchema } from '@fastgpt/global/core/chat/type'; +import type { AppSchema } from '@fastgpt/global/core/app/type'; import { ChatModelType } from '@/constants/model'; - -export type AppListItemType = { - _id: string; - name: string; - avatar: string; - intro: string; -}; - -export type CreateAppParams = { - name?: string; - avatar?: string; - type?: `${AppTypeEnum}`; - modules: AppSchema['modules']; -}; -export interface AppUpdateParams { - name?: string; - type?: `${AppTypeEnum}`; - avatar?: string; - intro?: string; - share?: AppSchema['share']; - modules?: AppSchema['modules']; -} +import { Text2SpeechVoiceEnum } from '@fastgpt/global/core/ai/speech/constant'; export interface ShareAppItem { _id: string; @@ -47,23 +27,6 @@ export interface ShareAppItem { isCollection: boolean; } -/* agent */ -/* question classify */ -export type ClassifyQuestionAgentItemType = { - value: string; - key: string; -}; -export type ContextExtractAgentItemType = { - desc: string; - key: string; - required: boolean; -}; -export type HttpFieldItemType = { - label: string; - key: string; - type: `${FlowNodeValTypeEnum}`; -}; - export type VariableItemType = { id: string; key: string; @@ -74,6 +37,13 @@ export type VariableItemType = { enums: { value: string }[]; }; +export type AppTTSConfigType = { + type: 'none' | 'web' | 'model'; + model?: string; + voice?: `${Text2SpeechVoiceEnum}`; + speed?: number; +}; + /* app module */ export type AppItemType = { id: string; @@ -104,6 +74,7 @@ export type RunningModuleItemType = { }; export type AppLogsListItemType = { + _id: string; id: string; source: ChatSchema['source']; time: Date; diff --git a/projects/app/src/types/chat.d.ts b/projects/app/src/types/chat.d.ts index c3e1a3707..d0d990c59 100644 --- a/projects/app/src/types/chat.d.ts +++ b/projects/app/src/types/chat.d.ts @@ -1,84 +1 @@ -import { ChatRoleEnum } from '@/constants/chat'; -import type { InitChatResponse } from '@/global/core/api/chatRes.d'; -import type { InitShareChatResponse } from '@/global/support/api/outLinkRes.d'; -import { TaskResponseKeyEnum } from '@/constants/chat'; -import { ClassifyQuestionAgentItemType } from './app'; -import { ChatItemSchema } from './mongoSchema'; -import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant'; -import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type'; - export type ExportChatType = 'md' | 'pdf' | 'html'; - -export type ChatItemType = { - dataId?: string; - obj: `${ChatRoleEnum}`; - value: string; - userFeedback?: string; - adminFeedback?: ChatItemSchema['adminFeedback']; - [TaskResponseKeyEnum.responseData]?: ChatHistoryItemResType[]; -}; - -export type ChatSiteItemType = { - status: 'loading' | 'running' | 'finish'; - moduleName?: string; -} & ChatItemType; - -export type HistoryItemType = { - chatId: string; - updateTime: Date; - customTitle?: string; - title: string; -}; -export type ChatHistoryItemType = HistoryItemType & { - appId: string; - top: boolean; -}; - -export type ShareChatHistoryItemType = HistoryItemType & { - shareId: string; - variables?: Record; - chats: ChatSiteItemType[]; -}; - -export type ShareChatType = InitShareChatResponse & { - history: ShareChatHistoryItemType; -}; - -// response data -export type moduleDispatchResType = { - price: number; - runningTime?: number; - tokens?: number; - model?: string; - - // chat - question?: string; - temperature?: number; - maxToken?: number; - quoteList?: SearchDataResponseItemType[]; - historyPreview?: ChatItemType[]; // completion context array. history will slice - - // dataset search - similarity?: number; - limit?: number; - - // cq - cqList?: ClassifyQuestionAgentItemType[]; - cqResult?: string; - - // content extract - extractDescription?: string; - extractResult?: Record; - - // http - body?: Record; - httpResult?: Record; - - // plugin output - pluginOutput?: Record; -}; -export type ChatHistoryItemResType = moduleDispatchResType & { - moduleType: `${FlowNodeTypeEnum}`; - moduleName: string; - moduleLogo?: string; -}; diff --git a/projects/app/src/types/common/bill.d.ts b/projects/app/src/types/common/bill.d.ts deleted file mode 100644 index 3d7c6413a..000000000 --- a/projects/app/src/types/common/bill.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { BillSourceEnum } from '@/constants/user'; -import type { BillListItemType } from '@/types/common/bill'; - -export type BillListItemType = { - moduleName: string; - amount: number; - model?: string; - tokenLen?: number; -}; - -export type CreateBillType = { - userId: string; - appName: string; - appId?: string; - total: number; - source: `${BillSourceEnum}`; - list: BillListItemType[]; -}; - -export type BillSchema = CreateBillType & { - _id: string; - time: Date; -}; diff --git a/projects/app/src/types/core/chat/type.d.ts b/projects/app/src/types/core/chat/type.d.ts index 4e0dbdc44..45eae4bfa 100644 --- a/projects/app/src/types/core/chat/type.d.ts +++ b/projects/app/src/types/core/chat/type.d.ts @@ -1,17 +1,17 @@ -import type { ChatCompletionRequestMessage } from '@fastgpt/global/core/ai/type.d'; import type { NextApiResponse } from 'next'; import { RunningModuleItemType } from '@/types/app'; -import type { UserModelSchema } from '@fastgpt/global/support/user/type'; - -export type MessageItemType = ChatCompletionRequestMessage & { dataId?: string; content: string }; +import type { UserType } from '@fastgpt/global/support/user/type'; // module dispatch props type export type ModuleDispatchProps = { res: NextApiResponse; + chatId?: string; stream: boolean; detail: boolean; variables: Record; outputs: RunningModuleItemType['outputs']; - user: UserModelSchema; + user: UserType; + teamId: string; + tmbId: string; inputs: T; }; diff --git a/projects/app/src/types/core/dataset/index.d.ts b/projects/app/src/types/core/dataset/index.d.ts index 992ed5da3..780186a3f 100644 --- a/projects/app/src/types/core/dataset/index.d.ts +++ b/projects/app/src/types/core/dataset/index.d.ts @@ -1,17 +1,11 @@ import { VectorModelItemType } from '../../model'; import type { DatasetSchemaType } from '@fastgpt/global/core/dataset/type.d'; -export type DatasetsItemType = Omit & { - vectorModel: VectorModelItemType; -}; - -export type DatasetItemType = { - _id: string; - avatar: string; - name: string; - userId: string; - vectorModel: VectorModelItemType; +export type DatasetItemType = Omit & { tags: string; + vectorModel: VectorModelItemType; + isOwner: boolean; + canWrite: boolean; }; export type DatasetPathItemType = { diff --git a/projects/app/src/types/i18n.d.ts b/projects/app/src/types/i18n.d.ts index 94aa80499..ac61588b9 100644 --- a/projects/app/src/types/i18n.d.ts +++ b/projects/app/src/types/i18n.d.ts @@ -1,13 +1,14 @@ import 'i18next'; -import common from '../../public/locales/en/common.json'; +// import common from '../../public/locales/en/common.json'; + +interface I18nNamespaces { + common: any; +} declare module 'i18next' { interface CustomTypeOptions { returnNull: false; - } - interface Resources { - [key: string]: { - [key: string]: string; - }; + defaultNs: 'common'; + // resources: I18nNamespaces; } } diff --git a/projects/app/src/types/index.d.ts b/projects/app/src/types/index.d.ts index 1b9d9b8a2..d0c23463e 100644 --- a/projects/app/src/types/index.d.ts +++ b/projects/app/src/types/index.d.ts @@ -1,11 +1,11 @@ -import type { Pool } from 'pg'; import type { Tiktoken } from 'js-tiktoken'; import { + AudioSpeechModelType, ChatModelItemType, FunctionModelItemType, LLMModelItemType, VectorModelItemType -} from './model'; +} from '@fastgpt/global/core/ai/model.d'; import { TrackEventName } from '@/constants/common'; export type PagingData = { @@ -18,20 +18,17 @@ export type PagingData = { export type RequestPaging = { pageNum: number; pageSize: number; [key]: any }; declare global { - var pgClient: Pool | null; var qaQueueLen: number; var vectorQueueLen: number; var TikToken: Tiktoken; - var sendInformQueue: (() => Promise)[]; - var sendInformQueueLen: number; - var vectorModels: VectorModelItemType[]; var chatModels: ChatModelItemType[]; var qaModels: LLMModelItemType[]; var cqModels: FunctionModelItemType[]; var extractModels: FunctionModelItemType[]; var qgModels: LLMModelItemType[]; + var audioSpeechModels: AudioSpeechModelType[]; var priceMd: string; var systemVersion: string; diff --git a/projects/app/src/types/mongoSchema.d.ts b/projects/app/src/types/mongoSchema.d.ts deleted file mode 100644 index c6f8a1ac2..000000000 --- a/projects/app/src/types/mongoSchema.d.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { ChatItemType } from './chat'; -import { ModelNameEnum, ChatModelType, EmbeddingModelType } from '@/constants/model'; -import type { DataType } from './data'; -import { InformTypeEnum } from '@/constants/user'; -import { TrainingModeEnum } from '@/constants/plugin'; -import type { ModuleItemType } from '@fastgpt/global/core/module/type'; -import { ChatSourceEnum } from '@/constants/chat'; -import { AppTypeEnum } from '@/constants/app'; -import { MarkDataType } from '@/global/core/dataset/type'; - -export interface AuthCodeSchema { - _id: string; - username: string; - code: string; - type: 'register' | 'findPassword'; - expiredTime: number; -} - -export interface AppSchema { - _id: string; - userId: string; - name: string; - type: `${AppTypeEnum}`; - avatar: string; - intro: string; - updateTime: number; - share: { - isShare: boolean; - isShareDetail: boolean; - collection: number; - }; - modules: ModuleItemType[]; -} - -export interface CollectionSchema { - appId: string; - userId: string; -} - -export interface ChatSchema { - _id: string; - chatId: string; - userId: string; - appId: string; - updateTime: Date; - title: string; - customTitle: string; - top: boolean; - variables: Record; - source: `${ChatSourceEnum}`; - shareId?: string; - isInit: boolean; - content: ChatItemType[]; -} - -export interface ChatItemSchema extends ChatItemType { - dataId: string; - chatId: string; - userId: string; - appId: string; - time: Date; - userFeedback?: string; - adminFeedback?: MarkDataType; -} diff --git a/projects/app/src/types/user.d.ts b/projects/app/src/types/user.d.ts index 41201d838..cad515972 100644 --- a/projects/app/src/types/user.d.ts +++ b/projects/app/src/types/user.d.ts @@ -1,16 +1,6 @@ -import { BillSourceEnum } from '@/constants/user'; +import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants'; import type { UserModelSchema } from '@fastgpt/global/support/user/type'; -import type { BillSchema } from '@/types/common/bill'; - -export interface UserType { - _id: string; - username: string; - avatar: string; - balance: number; - timezone: string; - promotionRate: UserModelSchema['promotionRate']; - openaiAccount: UserModelSchema['openaiAccount']; -} +import { BillSchema } from '@fastgpt/global/support/wallet/bill/type.d'; export interface UserUpdateParams { balance?: number; @@ -18,12 +8,3 @@ export interface UserUpdateParams { timezone?: string; openaiAccount?: UserModelSchema['openaiAccount']; } - -export interface UserBillType { - id: string; - time: Date; - appName: string; - source: BillSchema['source']; - total: number; - list: BillSchema['list']; -} diff --git a/projects/app/src/utils/adapt.ts b/projects/app/src/utils/adapt.ts index d874b8435..49208ed24 100644 --- a/projects/app/src/utils/adapt.ts +++ b/projects/app/src/utils/adapt.ts @@ -1,39 +1,17 @@ -import { formatPrice } from '@fastgpt/global/common/bill/tools'; -import type { BillSchema } from '@/types/common/bill'; -import type { UserBillType } from '@/types/user'; -import { ChatItemType } from '@/types/chat'; -import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constant'; -import { ChatRoleEnum } from '@/constants/chat'; -import type { MessageItemType } from '@/types/core/chat/type'; +import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; +import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d'; import type { ModuleItemType, FlowModuleItemType } from '@fastgpt/global/core/module/type.d'; import type { Edge, Node } from 'reactflow'; import { connectionLineStyle } from '@/constants/flow'; import { customAlphabet } from 'nanoid'; import { EmptyModule, ModuleTemplatesFlat } from '@/constants/flow/ModuleTemplate'; +import { adaptRole_Message2Chat } from './common/adapt/message'; const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6); -export const adaptBill = (bill: BillSchema): UserBillType => { - return { - id: bill._id, - source: bill.source, - time: bill.time, - total: formatPrice(bill.total), - appName: bill.appName, - list: bill.list - }; -}; - -export const gptMessage2ChatType = (messages: MessageItemType[]): ChatItemType[] => { - const roleMap = { - [ChatCompletionRequestMessageRoleEnum.Assistant]: ChatRoleEnum.AI, - [ChatCompletionRequestMessageRoleEnum.User]: ChatRoleEnum.Human, - [ChatCompletionRequestMessageRoleEnum.System]: ChatRoleEnum.System, - [ChatCompletionRequestMessageRoleEnum.Function]: ChatRoleEnum.Human - }; - +export const gptMessage2ChatType = (messages: ChatMessageItemType[]): ChatItemType[] => { return messages.map((item) => ({ dataId: item.dataId, - obj: roleMap[item.role], + obj: adaptRole_Message2Chat(item.role), value: item.content || '' })); }; diff --git a/projects/app/src/utils/common/adapt/message.ts b/projects/app/src/utils/common/adapt/message.ts index 084680782..88524851e 100644 --- a/projects/app/src/utils/common/adapt/message.ts +++ b/projects/app/src/utils/common/adapt/message.ts @@ -1,18 +1,21 @@ -import type { ChatItemType } from '@/types/chat'; -import { ChatRoleEnum } from '@/constants/chat'; +import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constant'; -import type { MessageItemType } from '@/types/core/chat/type'; +import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d'; const chat2Message = { [ChatRoleEnum.AI]: ChatCompletionRequestMessageRoleEnum.Assistant, [ChatRoleEnum.Human]: ChatCompletionRequestMessageRoleEnum.User, - [ChatRoleEnum.System]: ChatCompletionRequestMessageRoleEnum.System + [ChatRoleEnum.System]: ChatCompletionRequestMessageRoleEnum.System, + [ChatRoleEnum.Function]: ChatCompletionRequestMessageRoleEnum.Function, + [ChatRoleEnum.Tool]: ChatCompletionRequestMessageRoleEnum.Tool }; const message2Chat = { [ChatCompletionRequestMessageRoleEnum.System]: ChatRoleEnum.System, [ChatCompletionRequestMessageRoleEnum.User]: ChatRoleEnum.Human, [ChatCompletionRequestMessageRoleEnum.Assistant]: ChatRoleEnum.AI, - [ChatCompletionRequestMessageRoleEnum.Function]: 'function' + [ChatCompletionRequestMessageRoleEnum.Function]: ChatRoleEnum.Function, + [ChatCompletionRequestMessageRoleEnum.Tool]: ChatRoleEnum.Tool }; export function adaptRole_Chat2Message(role: `${ChatRoleEnum}`) { @@ -28,10 +31,10 @@ export const adaptChat2GptMessages = ({ }: { messages: ChatItemType[]; reserveId: boolean; -}): MessageItemType[] => { +}): ChatMessageItemType[] => { return messages.map((item) => ({ ...(reserveId && { dataId: item.dataId }), - role: chat2Message[item.obj] || ChatCompletionRequestMessageRoleEnum.System, + role: chat2Message[item.obj], content: item.value || '' })); }; diff --git a/projects/app/src/utils/service/core/chat/index.ts b/projects/app/src/utils/service/core/chat/index.ts index 230c0f06d..180fc9af1 100644 --- a/projects/app/src/utils/service/core/chat/index.ts +++ b/projects/app/src/utils/service/core/chat/index.ts @@ -1,4 +1,4 @@ -import { ChatHistoryItemResType } from '@/types/chat'; +import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d'; export function selectShareResponse({ responseData }: { responseData: ChatHistoryItemResType[] }) { const filedList = [ diff --git a/projects/app/src/web/common/api/fetch.ts b/projects/app/src/web/common/api/fetch.ts index ca1741491..92cf5cb16 100644 --- a/projects/app/src/web/common/api/fetch.ts +++ b/projects/app/src/web/common/api/fetch.ts @@ -1,7 +1,8 @@ -import { sseResponseEventEnum, TaskResponseKeyEnum } from '@/constants/chat'; +import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants'; +import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { parseStreamChunk, SSEParseData } from '@/utils/sse'; -import type { ChatHistoryItemResType } from '@/types/chat'; +import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d'; import { StartChatFnProps } from '@/components/ChatBox'; import { getToken } from '@/web/support/user/auth'; diff --git a/projects/app/src/web/common/bill/api.ts b/projects/app/src/web/common/bill/api.ts deleted file mode 100644 index 1c23d6d3b..000000000 --- a/projects/app/src/web/common/bill/api.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { GET, POST } from '@/web/common/api/request'; -import type { CreateTrainingBillType } from '@fastgpt/global/common/bill/types/billReq.d'; -import type { PaySchema } from '@fastgpt/global/support/wallet/type.d'; -import type { PagingData, RequestPaging } from '@/types'; -import { UserBillType } from '@/types/user'; -import { delay } from '@/utils/tools'; - -export const getUserBills = (data: RequestPaging) => - POST>(`/user/getBill`, data); - -export const postCreateTrainingBill = (data: CreateTrainingBillType) => - POST(`/common/bill/createTrainingBill`, data); - -export const getPayOrders = () => GET(`/user/getPayOrders`); - -export const getPayCode = (amount: number) => - GET<{ - codeUrl: string; - payId: string; - }>(`/plusApi/support/user/pay/getPayCode`, { amount }); - -export const checkPayResult = (payId: string) => - GET(`/plusApi/support/user/pay/checkPayResult`, { payId }).then(() => { - async function startQueue() { - try { - await GET('/user/account/paySuccess'); - } catch (error) { - await delay(1000); - startQueue(); - } - } - startQueue(); - return 'success'; - }); diff --git a/projects/app/src/web/common/file/api.ts b/projects/app/src/web/common/file/api.ts new file mode 100644 index 000000000..ebd4a12de --- /dev/null +++ b/projects/app/src/web/common/file/api.ts @@ -0,0 +1,17 @@ +import { GET, POST, PUT, DELETE } from '@/web/common/api/request'; +import { AxiosProgressEvent } from 'axios'; + +export const postUploadImg = (base64Img: string) => + POST('/common/file/uploadImage', { base64Img }); + +export const postUploadFiles = ( + data: FormData, + onUploadProgress: (progressEvent: AxiosProgressEvent) => void +) => + POST('/common/file/upload', data, { + timeout: 60000, + onUploadProgress, + headers: { + 'Content-Type': 'multipart/form-data; charset=utf-8' + } + }); diff --git a/projects/app/src/web/common/file/controller.ts b/projects/app/src/web/common/file/controller.ts new file mode 100644 index 000000000..f6a69d4ec --- /dev/null +++ b/projects/app/src/web/common/file/controller.ts @@ -0,0 +1,104 @@ +import { postUploadImg, postUploadFiles } from '@/web/common/file/api'; +import { BucketNameEnum } from '@fastgpt/global/common/file/constants'; + +/** + * upload file to mongo gridfs + */ +export const uploadFiles = ({ + files, + bucketName, + metadata = {}, + percentListen +}: { + files: File[]; + bucketName: `${BucketNameEnum}`; + metadata?: Record; + percentListen?: (percent: number) => void; +}) => { + const form = new FormData(); + form.append('metadata', JSON.stringify(metadata)); + form.append('bucketName', bucketName); + files.forEach((file) => { + form.append('file', file, encodeURIComponent(file.name)); + }); + return postUploadFiles(form, (e) => { + if (!e.total) return; + + const percent = Math.round((e.loaded / e.total) * 100); + percentListen && percentListen(percent); + }); +}; + +/** + * compress image. response base64 + * @param maxSize The max size of the compressed image + */ +export const compressImgAndUpload = ({ + file, + maxW = 200, + maxH = 200, + maxSize = 1024 * 100 +}: { + file: File; + maxW?: number; + maxH?: number; + maxSize?: number; +}) => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = async () => { + const img = new Image(); + // @ts-ignore + img.src = reader.result; + img.onload = async () => { + let width = img.width; + let height = img.height; + + if (width > height) { + if (width > maxW) { + height *= maxW / width; + width = maxW; + } + } else { + if (height > maxH) { + width *= maxH / height; + height = maxH; + } + } + + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + + if (!ctx) { + return reject('压缩图片异常'); + } + + ctx.drawImage(img, 0, 0, width, height); + const compressedDataUrl = canvas.toDataURL(file.type, 0.8); + // 移除 canvas 元素 + canvas.remove(); + + if (compressedDataUrl.length > maxSize) { + return reject('图片太大了'); + } + + const src = await (async () => { + try { + const src = await postUploadImg(compressedDataUrl); + return src; + } catch (error) { + return compressedDataUrl; + } + })(); + + resolve(src); + }; + }; + reader.onerror = (err) => { + console.log(err); + reject('压缩图片异常'); + }; + }); diff --git a/projects/app/src/web/common/file/utils.ts b/projects/app/src/web/common/file/utils.ts index f33710c07..903b6767a 100644 --- a/projects/app/src/web/common/file/utils.ts +++ b/projects/app/src/web/common/file/utils.ts @@ -1,27 +1,6 @@ import mammoth from 'mammoth'; import Papa from 'papaparse'; -import { postUploadImg, postUploadFiles, getFileViewUrl } from '@/web/common/system/api'; - -/** - * upload file to mongo gridfs - */ -export const uploadFiles = ( - files: File[], - metadata: Record = {}, - percentListen?: (percent: number) => void -) => { - const form = new FormData(); - form.append('metadata', JSON.stringify(metadata)); - files.forEach((file) => { - form.append('file', file, encodeURIComponent(file.name)); - }); - return postUploadFiles(form, (e) => { - if (!e.total) return; - - const percent = Math.round((e.loaded / e.total) * 100); - percentListen && percentListen(percent); - }); -}; +import { postUploadImg } from '@/web/common/file/api'; /** * 读取 txt 文件内容 @@ -147,7 +126,7 @@ export const readCsvContent = async (file: File) => { }; /** - * file download + * file download by text */ export const fileDownload = ({ text, @@ -169,15 +148,9 @@ export const fileDownload = ({ // 添加链接到页面并触发下载 document.body.appendChild(downloadLink); downloadLink.click(); - document.body.removeChild(downloadLink); + document.body?.removeChild(downloadLink); }; -export async function getFileAndOpen(fileId: string) { - const url = await getFileViewUrl(fileId); - const asPath = `${location.origin}${url}`; - window.open(asPath, '_blank'); -} - export const fileToBase64 = (file: File) => { return new Promise((resolve, reject) => { const reader = new FileReader(); @@ -186,77 +159,3 @@ export const fileToBase64 = (file: File) => { reader.onerror = (error) => reject(error); }); }; - -/** - * compress image. response base64 - * @param maxSize The max size of the compressed image - */ -export const compressImg = ({ - file, - maxW = 200, - maxH = 200, - maxSize = 1024 * 100 -}: { - file: File; - maxW?: number; - maxH?: number; - maxSize?: number; -}) => - new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = async () => { - const img = new Image(); - // @ts-ignore - img.src = reader.result; - img.onload = async () => { - let width = img.width; - let height = img.height; - - if (width > height) { - if (width > maxW) { - height *= maxW / width; - width = maxW; - } - } else { - if (height > maxH) { - width *= maxH / height; - height = maxH; - } - } - - const canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - const ctx = canvas.getContext('2d'); - - if (!ctx) { - return reject('压缩图片异常'); - } - - ctx.drawImage(img, 0, 0, width, height); - const compressedDataUrl = canvas.toDataURL(file.type, 0.8); - // 移除 canvas 元素 - canvas.remove(); - - if (compressedDataUrl.length > maxSize) { - return reject('图片太大了'); - } - - const src = await (async () => { - try { - const src = await postUploadImg(compressedDataUrl); - return src; - } catch (error) { - return compressedDataUrl; - } - })(); - - resolve(src); - }; - }; - reader.onerror = (err) => { - console.log(err); - reject('压缩图片异常'); - }; - }); diff --git a/projects/app/src/web/common/hooks/useConfirm.tsx b/projects/app/src/web/common/hooks/useConfirm.tsx index fe806c764..c2c5287fb 100644 --- a/projects/app/src/web/common/hooks/useConfirm.tsx +++ b/projects/app/src/web/common/hooks/useConfirm.tsx @@ -11,13 +11,14 @@ import { } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; -export const useConfirm = (props: { +export const useConfirm = (props?: { title?: string | null; content?: string | null; bg?: string; + showCancel?: boolean; }) => { const { t } = useTranslation(); - const { title = t('Warning'), content, bg } = props; + const { title = t('Warning'), content, bg, showCancel = true } = props || {}; const [customContent, setCustomContent] = useState(content); const { isOpen, onOpen, onClose } = useDisclosure(); @@ -52,18 +53,23 @@ export const useConfirm = (props: { {title} - {customContent} + + {customContent} + - + {showCancel && ( + + )} +
{t('wallet.bill.bill username')} {t('user.Time')} {t('user.Source')} {t('user.Application Name')}
{item.username} {dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')} {BillSourceMap[item.source]} {t(item.appName) || '-'}
e.stopPropagation()}> - - - - } - menuList={[ - { - child: ( - - - {t('Move')} - - ), - onClick: () => setMoveCollectionData({ collectionId: collection._id }) - }, - { - child: ( - - - {t('Rename')} - - ), - onClick: () => - onOpenEditTitleModal({ - defaultVal: collection.name, - onSuccess: (newName) => { - onUpdateCollectionName({ - collectionId: collection._id, - name: newName - }); + _hover={{ + color: 'myBlue.600', + '& .icon': { + bg: 'myGray.100' } - }) - }, - { - child: ( - - - {t('common.Delete')} - - ), - onClick: () => - openConfirm( - () => { - onDelCollection(collection._id); - }, - undefined, - collection.type === DatasetCollectionTypeEnum.folder - ? t('dataset.collections.Confirm to delete the folder') - : t('dataset.Confirm to delete the file') - )() + }} + > + + } - ]} - /> + menuList={[ + { + child: ( + + + {t('Move')} + + ), + onClick: () => setMoveCollectionData({ collectionId: collection._id }) + }, + { + child: ( + + + {t('Rename')} + + ), + onClick: () => + onOpenEditTitleModal({ + defaultVal: collection.name, + onSuccess: (newName) => { + onUpdateCollectionName({ + collectionId: collection._id, + name: newName + }); + } + }) + }, + { + child: ( + + + {t('common.Delete')} + + ), + onClick: () => + openConfirm( + () => { + onDelCollection(collection._id); + }, + undefined, + collection.type === DatasetCollectionTypeEnum.folder + ? t('dataset.collections.Confirm to delete the folder') + : t('dataset.Confirm to delete the file') + )() + } + ]} + /> + )}