diff --git a/.github/workflows/preview-fastgpt-image.yml b/.github/workflows/preview-fastgpt-image.yml index 4db6e43ad61e..9e5780112788 100644 --- a/.github/workflows/preview-fastgpt-image.yml +++ b/.github/workflows/preview-fastgpt-image.yml @@ -36,7 +36,7 @@ jobs: password: ${{ secrets.GH_PAT }} - name: Set DOCKER_REPO_TAGGED based on branch or tag run: | - echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-pr:${{ github.event.pull_request.number }}" >> $GITHUB_ENV + echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-pr:${{ github.event.pull_request.head.sha }}" >> $GITHUB_ENV - name: Build image for PR env: DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }} @@ -44,7 +44,7 @@ jobs: docker buildx build \ -f projects/app/Dockerfile \ --label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \ - --label "org.opencontainers.image.description=fastgpt-pr imae" \ + --label "org.opencontainers.image.description=fastgpt-pr image" \ --label "org.opencontainers.image.licenses=Apache" \ --push \ --cache-from=type=local,src=/tmp/.buildx-cache \ diff --git a/dev.md b/dev.md index 373ac626b783..2a58d8d30a66 100644 --- a/dev.md +++ b/dev.md @@ -1,6 +1,6 @@ ## Premise -Since FastGPT is managed in the same way as monorepo, it is recommended to install 'make' first during development. +Since FastGPT is managed in the same way as monorepo, it is recommended to install ‘make’ first during development. monorepo Project Name: diff --git a/docSite/assets/imgs/dataset1.png b/docSite/assets/imgs/dataset1.png new file mode 100644 index 000000000000..c521ba681257 Binary files /dev/null and b/docSite/assets/imgs/dataset1.png differ diff --git a/docSite/assets/imgs/dataset2.png b/docSite/assets/imgs/dataset2.png new file mode 100644 index 000000000000..5bbe11676e31 Binary files /dev/null and b/docSite/assets/imgs/dataset2.png differ diff --git a/docSite/assets/imgs/template_submission2.png b/docSite/assets/imgs/template_submission2.png index 338d47b1a119..14ca7b91fe35 100644 Binary files a/docSite/assets/imgs/template_submission2.png and b/docSite/assets/imgs/template_submission2.png differ diff --git a/docSite/content/zh-cn/docs/development/faq.md b/docSite/content/zh-cn/docs/development/faq.md index 8f15f7a975cf..52582e8b1bfc 100644 --- a/docSite/content/zh-cn/docs/development/faq.md +++ b/docSite/content/zh-cn/docs/development/faq.md @@ -19,6 +19,10 @@ images: [] ## 二、通用问题 +### 本地部署的限制 + +具体内容参考https://fael3z0zfze.feishu.cn/wiki/OFpAw8XzAi36Guk8dfucrCKUnjg。 + ### 能否纯本地运行 可以。需要准备好向量模型和LLM模型。 diff --git a/docSite/content/zh-cn/docs/development/upgrading/4818.md b/docSite/content/zh-cn/docs/development/upgrading/4818.md new file mode 100644 index 000000000000..0f102c2d6181 --- /dev/null +++ b/docSite/content/zh-cn/docs/development/upgrading/4818.md @@ -0,0 +1,37 @@ +--- +title: 'V4.8.18(进行中)' +description: 'FastGPT V4.8.18 更新说明' +icon: 'upgrade' +draft: false +toc: true +weight: 806 +--- + +## 更新指南 + +### 2. 运行升级脚本 + +从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`;{{host}} 替换成**FastGPT 域名**。 + +```bash +curl --location --request POST 'https://{{host}}/api/admin/initv4818' \ +--header 'rootkey: {{rootkey}}' \ +--header 'Content-Type: application/json' +``` + +会迁移全文检索表,时间较长,迁移期间全文检索会失效,日志中会打印已经迁移的数据长度。 + + +## 完整更新内容 + +1. 新增 - 支持部门架构权限模式。 +2. 新增 - 支持配置自定跨域安全策略,默认全开。 +3. 优化 - 分享链接随机生成用户头像。 +4. 优化 - 图片上传安全校验。并增加头像图片唯一存储,确保不会累计存储。 +5. 优化 - Mongo 全文索引表分离。 +6. 优化 - 知识库检索查询语句合并,同时减少查库数量。 +7. 优化 - 文件编码检测,减少 CSV 文件乱码概率。 +8. 优化 - 异步读取文件内容,减少进程阻塞。 +9. 优化 - 文件阅读,HTML 直接下载,不允许在线阅读。 +10. 修复 - HTML 文件上传,base64 图片无法自动转图片链接。 +11. 修复 - 插件计费错误。 diff --git a/docSite/content/zh-cn/docs/faq/app.md b/docSite/content/zh-cn/docs/faq/app.md index 41e4272761ee..7631fb7836ae 100644 --- a/docSite/content/zh-cn/docs/faq/app.md +++ b/docSite/content/zh-cn/docs/faq/app.md @@ -13,4 +13,12 @@ weight: 908 但是,当连续问题之间的关联性较小,模型判断的准确度可能会受到限制。在这种情况下,我们可以引入全局变量的概念来记录分类结果。在后续的问题分类阶段,首先检查全局变量是否存有分类结果。如果有,那么直接沿用该结果;若没有,则让模型自行判断。 -建议:构建批量运行脚本进行测试,评估问题分类的准确性。 \ No newline at end of file +建议:构建批量运行脚本进行测试,评估问题分类的准确性。 + +## 系统编排配置中的定时执行,如果用户打开分享的连接,停留在那个页面,定时执行触发问题 + +发布后,后台生效。 + +## AI对话回答要求中的Markdown语法取消 + +在针对知识库的回答要求里有, 要给它配置提示词,不然他就是默认的,默认的里面就有该语法。 \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/faq/dataset.md b/docSite/content/zh-cn/docs/faq/dataset.md index e66ebc01ec32..589b57160660 100644 --- a/docSite/content/zh-cn/docs/faq/dataset.md +++ b/docSite/content/zh-cn/docs/faq/dataset.md @@ -14,4 +14,51 @@ weight: 910 ## 知识库配置里的文件处理模型是什么?与索引模型有什么区别? * **文件处理模型**:用于数据处理的【增强处理】和【问答拆分】。在【增强处理】中,生成相关问题和摘要,在【问答拆分】中执行问答对生成。 -* **索引模型**:用于向量化,即通过对文本数据进行处理和组织,构建出一个能够快速查询的数据结构。 \ No newline at end of file +* **索引模型**:用于向量化,即通过对文本数据进行处理和组织,构建出一个能够快速查询的数据结构。 + +## 基于知识库的查询,但是问题相关的答案过多。ai回答到一半就不继续回答。 + +FastGPT回复长度计算公式: + +最大回复=min(配置的最大回复(内置的限制),最大上下文(输入和输出的总和)-历史记录) + +18K模型->输入与输出的和 + +输出增多->输入减小 + +所以可以: + +1. 检查配置的最大回复(回复上限) +2. 减小输入来增大输出,即减小历史记录,在工作流其实也就是“聊天记录” + +配置的最大回复: + +![](/imgs/dataset1.png) + +![](/imgs/dataset2.png) + +1. 私有化部署的时候,后台配模型参数,可以在配置最大上文时候,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出 + + +## 受到模型上下文的限制,有时候达不到聊天记录的轮次,连续对话字数过多就会报上下文不够的错误。 + +FastGPT回复长度计算公式: + +最大回复=min(配置的最大回复(内置的限制),最大上下文(输入和输出的总和)-历史记录) + +18K模型->输入与输出的和 + +输出增多->输入减小 + +所以可以: + +1. 检查配置的最大回复(回复上限) +2. 减小输入来增大输出,即减小历史记录,在工作流其实也就是“聊天记录” + +配置的最大回复: + +![](/imgs/dataset1.png) + +![](/imgs/dataset2.png) + +1. 私有化部署的时候,后台配模型参数,可以在配置最大上文时候,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出 \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/guide/plugins/searxng_plugin_guide.md b/docSite/content/zh-cn/docs/guide/plugins/searxng_plugin_guide.md index 36e1d2c60694..6652d7ff1458 100644 --- a/docSite/content/zh-cn/docs/guide/plugins/searxng_plugin_guide.md +++ b/docSite/content/zh-cn/docs/guide/plugins/searxng_plugin_guide.md @@ -160,6 +160,18 @@ default_doi_resolver: 'oadoi.org' } ``` +* 搜索结果为空时会返回友好提示: + +```Bash +{ + "result": "[]", + "error": { + "message": "No search results", + "code": 500 + } +} +``` + * 失败时通过 Promise.reject 可能返回错误信息: ```Bash diff --git a/docSite/content/zh-cn/docs/use-cases/app-cases/submit_application_template.md b/docSite/content/zh-cn/docs/use-cases/app-cases/submit_application_template.md index 1df015e977c9..2ef61eaf947e 100644 --- a/docSite/content/zh-cn/docs/use-cases/app-cases/submit_application_template.md +++ b/docSite/content/zh-cn/docs/use-cases/app-cases/submit_application_template.md @@ -32,11 +32,11 @@ weight: 602 1. ### 创建应用模板 -应用模板配置以及相关资源,都会在 **projects/app/public/appMarketTemplates** 目录下。 +应用模板配置以及相关资源,都会在 **packages/templates/src** 目录下。 ![](/imgs/template_submission2.png) -1. 在 **projects/app/public/appMarketTemplates** 目录下,创建一个文件夹,名称为模板对应的 id。 +1. 在**packages/templates/src** 目录下,创建一个文件夹,名称为模板对应的 id。 2. 在刚刚创建的文件夹中,再创建一个 **template.json** 文件,复制粘贴并填写如下配置: ```JSON @@ -83,4 +83,4 @@ weight: 602 - 写清楚模板的介绍和功能 - 配上模板运行的效果图 -- 模板参数填写说明,需要在 PR 中写清楚。例如,有些模板需要去某个提供商申请 key,需要附上对应的地址和教程,后续我们会加入到文档中。 \ No newline at end of file +- 模板参数填写说明,需要在 PR 中写清楚。例如,有些模板需要去某个提供商申请 key,需要附上对应的地址和教程,后续我们会加入到文档中。 diff --git a/packages/global/common/error/code/common.ts b/packages/global/common/error/code/common.ts index 13f74643b6bc..38d76dbec009 100644 --- a/packages/global/common/error/code/common.ts +++ b/packages/global/common/error/code/common.ts @@ -1,14 +1,20 @@ +import { i18nT } from '../../../../web/i18n/utils'; import { ErrType } from '../errorCode'; /* dataset: 507000 */ const startCode = 507000; export enum CommonErrEnum { + invalidParams = 'invalidParams', fileNotFound = 'fileNotFound', unAuthFile = 'unAuthFile', missingParams = 'missingParams', inheritPermissionError = 'inheritPermissionError' } const datasetErr = [ + { + statusText: CommonErrEnum.fileNotFound, + message: i18nT('common:error.invalid_params') + }, { statusText: CommonErrEnum.fileNotFound, message: 'error.fileNotFound' diff --git a/packages/global/common/error/code/team.ts b/packages/global/common/error/code/team.ts index 8ad9abe021a2..7c2ab6790897 100644 --- a/packages/global/common/error/code/team.ts +++ b/packages/global/common/error/code/team.ts @@ -1,9 +1,11 @@ -import { ErrType } from '../errorCode'; import { i18nT } from '../../../../web/i18n/utils'; +import type { ErrType } from '../errorCode'; /* team: 500000 */ export enum TeamErrEnum { + notUser = 'notUser', teamOverSize = 'teamOverSize', unAuthTeam = 'unAuthTeam', + teamMemberOverSize = 'teamMemberOverSize', aiPointsNotEnough = 'aiPointsNotEnough', datasetSizeNotEnough = 'datasetSizeNotEnough', datasetAmountNotEnough = 'datasetAmountNotEnough', @@ -14,11 +16,22 @@ export enum TeamErrEnum { groupNameEmpty = 'groupNameEmpty', groupNameDuplicate = 'groupNameDuplicate', groupNotExist = 'groupNotExist', + orgMemberNotExist = 'orgMemberNotExist', + orgMemberDuplicated = 'orgMemberDuplicated', + orgNotExist = 'orgNotExist', + orgParentNotExist = 'orgParentNotExist', + cannotMoveToSubPath = 'cannotMoveToSubPath', + cannotModifyRootOrg = 'cannotModifyRootOrg', + cannotDeleteNonEmptyOrg = 'cannotDeleteNonEmptyOrg', cannotDeleteDefaultGroup = 'cannotDeleteDefaultGroup', userNotActive = 'userNotActive' } const teamErr = [ + { + statusText: TeamErrEnum.notUser, + message: i18nT('common:code_error.team_error.not_user') + }, { statusText: TeamErrEnum.teamOverSize, message: i18nT('common:code_error.team_error.over_size') @@ -71,6 +84,34 @@ const teamErr = [ { statusText: TeamErrEnum.userNotActive, message: i18nT('common:code_error.team_error.user_not_active') + }, + { + statusText: TeamErrEnum.orgMemberNotExist, + message: i18nT('common:code_error.team_error.org_member_not_exist') + }, + { + statusText: TeamErrEnum.orgMemberDuplicated, + message: i18nT('common:code_error.team_error.org_member_duplicated') + }, + { + statusText: TeamErrEnum.orgNotExist, + message: i18nT('common:code_error.team_error.org_not_exist') + }, + { + statusText: TeamErrEnum.orgParentNotExist, + message: i18nT('common:code_error.team_error.org_parent_not_exist') + }, + { + statusText: TeamErrEnum.cannotMoveToSubPath, + message: i18nT('common:code_error.team_error.cannot_move_to_sub_path') + }, + { + statusText: TeamErrEnum.cannotModifyRootOrg, + message: i18nT('common:code_error.team_error.cannot_modify_root_org') + }, + { + statusText: TeamErrEnum.cannotDeleteNonEmptyOrg, + message: i18nT('common:code_error.team_error.cannot_delete_non_empty_org') } ]; diff --git a/packages/global/common/error/code/user.ts b/packages/global/common/error/code/user.ts index e58136041b80..98d8e9a0f996 100644 --- a/packages/global/common/error/code/user.ts +++ b/packages/global/common/error/code/user.ts @@ -2,25 +2,16 @@ import { ErrType } from '../errorCode'; import { i18nT } from '../../../../web/i18n/utils'; /* team: 503000 */ export enum UserErrEnum { - unAuthUser = 'unAuthUser', unAuthRole = 'unAuthRole', - binVisitor = 'binVisitor', + account_psw_error = 'account_psw_error', balanceNotEnough = 'balanceNotEnough', unAuthSso = 'unAuthSso' } const errList = [ { - statusText: UserErrEnum.unAuthUser, - message: i18nT('common:code_error.user_error.un_auth_user') + statusText: UserErrEnum.account_psw_error, + message: i18nT('common:code_error.account_error') }, - { - statusText: UserErrEnum.binVisitor, - message: i18nT('common:code_error.user_error.bin_visitor') - }, // 身份校验未通过 - { - statusText: UserErrEnum.binVisitor, - message: i18nT('common:code_error.user_error.bin_visitor_guest') - }, // 游客身份 { statusText: UserErrEnum.balanceNotEnough, message: i18nT('common:code_error.user_error.balance_not_enough') diff --git a/packages/global/common/file/api.d.ts b/packages/global/common/file/api.d.ts index e26033ae73ea..5da698b586c9 100644 --- a/packages/global/common/file/api.d.ts +++ b/packages/global/common/file/api.d.ts @@ -1,10 +1,7 @@ -import { MongoImageTypeEnum } from './image/constants'; import { OutLinkChatAuthProps } from '../../support/permission/chat.d'; export type preUploadImgProps = OutLinkChatAuthProps & { - type: `${MongoImageTypeEnum}`; - - expiredTime?: Date; + // expiredTime?: Date; metadata?: Record; }; export type UploadImgProps = preUploadImgProps & { diff --git a/packages/global/common/file/image/constants.ts b/packages/global/common/file/image/constants.ts index 6a178e2afcf6..5e511e4b26f5 100644 --- a/packages/global/common/file/image/constants.ts +++ b/packages/global/common/file/image/constants.ts @@ -1,61 +1,5 @@ export const imageBaseUrl = '/api/system/img/'; -export enum MongoImageTypeEnum { - systemAvatar = 'systemAvatar', - appAvatar = 'appAvatar', - pluginAvatar = 'pluginAvatar', - datasetAvatar = 'datasetAvatar', - userAvatar = 'userAvatar', - teamAvatar = 'teamAvatar', - groupAvatar = 'groupAvatar', - - chatImage = 'chatImage', - collectionImage = 'collectionImage' -} -export const mongoImageTypeMap = { - [MongoImageTypeEnum.systemAvatar]: { - label: 'appAvatar', - unique: true - }, - [MongoImageTypeEnum.appAvatar]: { - label: 'appAvatar', - unique: true - }, - [MongoImageTypeEnum.pluginAvatar]: { - label: 'pluginAvatar', - unique: true - }, - [MongoImageTypeEnum.datasetAvatar]: { - label: 'datasetAvatar', - unique: true - }, - [MongoImageTypeEnum.userAvatar]: { - label: 'userAvatar', - unique: true - }, - [MongoImageTypeEnum.teamAvatar]: { - label: 'teamAvatar', - unique: true - }, - [MongoImageTypeEnum.groupAvatar]: { - label: 'groupAvatar', - unique: true - }, - - [MongoImageTypeEnum.chatImage]: { - label: 'chatImage', - unique: false - }, - [MongoImageTypeEnum.collectionImage]: { - label: 'collectionImage', - unique: false - } -}; - -export const uniqueImageTypeList = Object.entries(mongoImageTypeMap) - .filter(([key, value]) => value.unique) - .map(([key]) => key as `${MongoImageTypeEnum}`); - export const FolderIcon = 'file/fill/folder'; export const FolderImgUrl = '/imgs/files/folder.svg'; export const HttpPluginImgUrl = '/imgs/app/httpPluginFill.svg'; diff --git a/packages/global/common/file/image/type.d.ts b/packages/global/common/file/image/type.d.ts index 6fe274794bfc..320df97fdfa6 100644 --- a/packages/global/common/file/image/type.d.ts +++ b/packages/global/common/file/image/type.d.ts @@ -1,12 +1,8 @@ -import { MongoImageTypeEnum } from './constants'; - export type MongoImageSchemaType = { _id: string; teamId: string; binary: Buffer; - createTime: Date; expiredTime?: Date; - type: `${MongoImageTypeEnum}`; metadata?: { mime?: string; // image mime type. diff --git a/packages/global/common/file/tools.ts b/packages/global/common/file/tools.ts index df0de53c0507..4f75f4e5d13e 100644 --- a/packages/global/common/file/tools.ts +++ b/packages/global/common/file/tools.ts @@ -2,6 +2,7 @@ import { detect } from 'jschardet'; import { documentFileType, imageFileType } from './constants'; import { ChatFileTypeEnum } from '../../core/chat/constants'; import { UserChatItemValueItemType } from '../../core/chat/type'; +import * as fs from 'fs'; export const formatFileSize = (bytes: number): string => { if (bytes === 0) return '0 B'; @@ -16,6 +17,22 @@ export const formatFileSize = (bytes: number): string => { export const detectFileEncoding = (buffer: Buffer) => { return detect(buffer.slice(0, 200))?.encoding?.toLocaleLowerCase(); }; +export const detectFileEncodingByPath = async (path: string) => { + // Get 64KB file head + const MAX_BYTES = 64 * 1024; + const buffer = Buffer.alloc(MAX_BYTES); + + const fd = await fs.promises.open(path, 'r'); + try { + // Read file head + const { bytesRead } = await fd.read(buffer, 0, MAX_BYTES, 0); + const actualBuffer = buffer.slice(0, bytesRead); + + return detect(actualBuffer)?.encoding?.toLocaleLowerCase(); + } finally { + await fd.close(); + } +}; // Url => user upload file type export const parseUrlToFileType = (url: string): UserChatItemValueItemType['file'] | undefined => { diff --git a/packages/global/common/system/constants.ts b/packages/global/common/system/constants.ts index 1ca44a6118d7..4dcc4d276666 100644 --- a/packages/global/common/system/constants.ts +++ b/packages/global/common/system/constants.ts @@ -1,6 +1,9 @@ export const HUMAN_ICON = `/icon/human.svg`; export const LOGO_ICON = `/icon/logo.svg`; export const HUGGING_FACE_ICON = `/imgs/model/huggingface.svg`; + export const DEFAULT_TEAM_AVATAR = `/imgs/avatar/defaultTeamAvatar.svg`; +export const DEFAULT_ORG_AVATAR = '/imgs/avatar/defaultOrgAvatar.svg'; +export const DEFAULT_USER_AVATAR = '/imgs/avatar/BlueAvatar.svg'; export const isProduction = process.env.NODE_ENV === 'production'; diff --git a/packages/global/common/system/types/index.d.ts b/packages/global/common/system/types/index.d.ts index 7cc24da41473..90bcb7dfd108 100644 --- a/packages/global/common/system/types/index.d.ts +++ b/packages/global/common/system/types/index.d.ts @@ -73,6 +73,11 @@ export type FastGPTFeConfigsType = { google?: string; wechat?: string; dingtalk?: string; + wecom?: { + corpid?: string; + agentid?: string; + secret?: string; + }; microsoft?: { clientId?: string; tenantId?: string; diff --git a/packages/global/core/ai/model.d.ts b/packages/global/core/ai/model.d.ts index 83f13eb812aa..533ced31d88a 100644 --- a/packages/global/core/ai/model.d.ts +++ b/packages/global/core/ai/model.d.ts @@ -53,6 +53,7 @@ export type VectorModelItemType = PriceType & { }; export type ReRankModelItemType = PriceType & { + provider: ModelProviderIdType; model: string; name: string; requestUrl: string; diff --git a/packages/global/core/app/collaborator.d.ts b/packages/global/core/app/collaborator.d.ts index ca0fec7217f0..66a456379d55 100644 --- a/packages/global/core/app/collaborator.d.ts +++ b/packages/global/core/app/collaborator.d.ts @@ -1,6 +1,6 @@ -import { RequireOnlyOne } from '../../common/type/utils'; +import type { RequireOnlyOne } from '../../common/type/utils'; import { - UpdateClbPermissionProps, + type UpdateClbPermissionProps, UpdatePermissionBody } from '../../support/permission/collaborator'; import { PermissionValueType } from '../../support/permission/type'; @@ -14,4 +14,5 @@ export type AppCollaboratorDeleteParams = { } & RequireOnlyOne<{ tmbId: string; groupId: string; + orgId: string; }>; diff --git a/packages/global/core/dataset/collaborator.d.ts b/packages/global/core/dataset/collaborator.d.ts index 7f33f4d516be..672430c1de22 100644 --- a/packages/global/core/dataset/collaborator.d.ts +++ b/packages/global/core/dataset/collaborator.d.ts @@ -11,4 +11,5 @@ export type DatasetCollaboratorDeleteParams = { } & RequireOnlyOne<{ tmbId: string; groupId: string; + orgId: string; }>; diff --git a/packages/global/core/dataset/type.d.ts b/packages/global/core/dataset/type.d.ts index 1b4dae45c1b2..7a772f78d481 100644 --- a/packages/global/core/dataset/type.d.ts +++ b/packages/global/core/dataset/type.d.ts @@ -112,6 +112,15 @@ export type DatasetDataSchemaType = { rebuilding?: boolean; }; +export type DatasetDataTextSchemaType = { + _id: string; + teamId: string; + datasetId: string; + collectionId: string; + dataId: string; + fullTextToken: string; +}; + export type DatasetTrainingSchemaType = { _id: string; userId: string; diff --git a/packages/global/support/permission/collaborator.d.ts b/packages/global/support/permission/collaborator.d.ts index 60a84a9d58e9..af7ee84e1a03 100644 --- a/packages/global/support/permission/collaborator.d.ts +++ b/packages/global/support/permission/collaborator.d.ts @@ -10,22 +10,18 @@ export type CollaboratorItemType = { } & RequireOnlyOne<{ tmbId: string; groupId: string; + orgId: string; }>; export type UpdateClbPermissionProps = { members?: string[]; groups?: string[]; + orgs?: string[]; permission: PermissionValueType; }; -export type DeleteClbPermissionProps = RequireOnlyOne<{ - tmbId: string; - groupId: string; -}>; - -export type UpdatePermissionBody = { - permission: PermissionValueType; -} & RequireOnlyOne<{ - memberId: string; - groupId: string; +export type DeletePermissionQuery = RequireOnlyOne<{ + tmbId?: string; + groupId?: string; + orgId?: string; }>; diff --git a/packages/global/support/permission/type.d.ts b/packages/global/support/permission/type.d.ts index 619f8503e67c..f473d2941060 100644 --- a/packages/global/support/permission/type.d.ts +++ b/packages/global/support/permission/type.d.ts @@ -1,8 +1,9 @@ import { UserModelSchema } from '../user/type'; import { RequireOnlyOne } from '../../common/type/utils'; import { TeamMemberSchema } from '../user/team/type'; -import { AuthUserTypeEnum, PermissionKeyEnum, PerResourceTypeEnum } from './constant'; import { MemberGroupSchemaType } from './memberGroup/type'; +import type { TeamMemberWithUserSchema } from '../user/team/type'; +import { AuthUserTypeEnum, type PermissionKeyEnum, type PerResourceTypeEnum } from './constant'; // PermissionValueType, the type of permission's value is a number, which is a bit field actually. // It is spired by the permission system in Linux. @@ -29,6 +30,7 @@ export type ResourcePermissionType = { } & RequireOnlyOne<{ tmbId: string; groupId: string; + orgId: string; }>; export type ResourcePerWithTmbWithUser = Omit & { diff --git a/packages/global/support/user/constant.ts b/packages/global/support/user/constant.ts index bd696afc7fef..8d8d24e27179 100644 --- a/packages/global/support/user/constant.ts +++ b/packages/global/support/user/constant.ts @@ -17,5 +17,6 @@ export enum OAuthEnum { wechat = 'wechat', microsoft = 'microsoft', dingtalk = 'dingtalk', + wecom = 'wecom', sso = 'sso' } diff --git a/packages/global/support/user/login/constants.ts b/packages/global/support/user/login/constants.ts new file mode 100644 index 000000000000..0093f93671c1 --- /dev/null +++ b/packages/global/support/user/login/constants.ts @@ -0,0 +1,3 @@ +export function checkIsWecomTerminal() { + return /wxwork/i.test(navigator.userAgent); +} diff --git a/packages/global/support/user/team/org/api.d.ts b/packages/global/support/user/team/org/api.d.ts new file mode 100644 index 000000000000..69ca09796225 --- /dev/null +++ b/packages/global/support/user/team/org/api.d.ts @@ -0,0 +1,32 @@ +export type postCreateOrgData = { + name: string; + parentId: string; + description?: string; + avatar?: string; +}; + +export type putUpdateOrgMembersData = { + orgId: string; + members: { + tmbId: string; + // role: `${OrgMemberRole}`; + }[]; +}; + +export type putUpdateOrgData = { + orgId: string; + name?: string; + avatar?: string; + description?: string; +}; + +export type putMoveOrgType = { + orgId: string; + targetOrgId: string; +}; + +// type putChnageOrgOwnerData = { +// orgId: string; +// tmbId: string; +// toAdmin?: boolean; +// }; diff --git a/packages/global/support/user/team/org/constant.ts b/packages/global/support/user/team/org/constant.ts new file mode 100644 index 000000000000..370cdad82901 --- /dev/null +++ b/packages/global/support/user/team/org/constant.ts @@ -0,0 +1,12 @@ +import { OrgSchemaType } from './type'; + +export const OrgCollectionName = 'team_orgs'; +export const OrgMemberCollectionName = 'team_org_members'; + +export const getOrgChildrenPath = (org: OrgSchemaType) => `${org.path}/${org.pathId}`; + +// export enum OrgMemberRole { +// owner = 'owner', +// admin = 'admin', +// member = 'member' +// } diff --git a/packages/global/support/user/team/org/type.d.ts b/packages/global/support/user/team/org/type.d.ts new file mode 100644 index 000000000000..ca4a73292272 --- /dev/null +++ b/packages/global/support/user/team/org/type.d.ts @@ -0,0 +1,25 @@ +import type { TeamPermission } from 'support/permission/user/controller'; +import { ResourcePermissionType } from '../type'; + +type OrgSchemaType = { + _id: string; + teamId: string; + pathId: string; + path: string; + name: string; + avatar?: string; + description?: string; + updateTime: Date; +}; + +type OrgMemberSchemaType = { + teamId: string; + orgId: string; + tmbId: string; +}; + +type OrgType = Omit & { + avatar: string; + members: OrgMemberSchemaType[]; + permission: TeamPermission; +}; diff --git a/packages/global/support/user/team/type.d.ts b/packages/global/support/user/team/type.d.ts index 9914e631a8d1..9c7dc12b42ba 100644 --- a/packages/global/support/user/team/type.d.ts +++ b/packages/global/support/user/team/type.d.ts @@ -55,10 +55,11 @@ export type TeamMemberWithTeamAndUserSchema = TeamMemberSchema & { export type TeamTmbItemType = { userId: string; teamId: string; + teamAvatar?: string; teamName: string; memberName: string; avatar: string; - balance: number; + balance?: number; tmbId: string; teamDomain: string; defaultTeam: boolean; diff --git a/packages/global/support/user/utils.ts b/packages/global/support/user/utils.ts new file mode 100644 index 000000000000..b0db78a92d95 --- /dev/null +++ b/packages/global/support/user/utils.ts @@ -0,0 +1,16 @@ +export const getRandomUserAvatar = () => { + const defaultAvatars = [ + '/imgs/avatar/RoyalBlueAvatar.svg', + '/imgs/avatar/PurpleAvatar.svg', + '/imgs/avatar/AdoraAvatar.svg', + '/imgs/avatar/OrangeAvatar.svg', + '/imgs/avatar/RedAvatar.svg', + '/imgs/avatar/GrayModernAvatar.svg', + '/imgs/avatar/TealAvatar.svg', + '/imgs/avatar/GreenAvatar.svg', + '/imgs/avatar/BrightBlueAvatar.svg', + '/imgs/avatar/BlueAvatar.svg' + ]; + + return defaultAvatars[Math.floor(Math.random() * defaultAvatars.length)]; +}; diff --git a/packages/plugins/register.ts b/packages/plugins/register.ts index 2509422d2f7b..9989fbc78761 100644 --- a/packages/plugins/register.ts +++ b/packages/plugins/register.ts @@ -35,24 +35,26 @@ export const list = [...staticPluginList, ...packagePluginList]; /* Get plugins */ export const getCommunityPlugins = () => { - return list.map((name) => { - const config = require(`./src/${name}/template.json`); + return Promise.all( + list.map>(async (name) => { + const config = (await import(`./src/${name}/template.json`))?.default; - const isFolder = list.find((item) => item.startsWith(`${name}/`)); + const isFolder = list.find((item) => item.startsWith(`${name}/`)); - const parentIdList = name.split('/').slice(0, -1); - const parentId = - parentIdList.length > 0 ? `${PluginSourceEnum.community}-${parentIdList.join('/')}` : null; + const parentIdList = name.split('/').slice(0, -1); + const parentId = + parentIdList.length > 0 ? `${PluginSourceEnum.community}-${parentIdList.join('/')}` : null; - return { - ...config, - id: `${PluginSourceEnum.community}-${name}`, - isFolder, - parentId, - isActive: true, - isOfficial: true - }; - }); + return { + ...config, + id: `${PluginSourceEnum.community}-${name}`, + isFolder, + parentId, + isActive: true, + isOfficial: true + }; + }) + ); }; export const getSystemPluginTemplates = () => { diff --git a/packages/plugins/src/searchXNG/index.ts b/packages/plugins/src/searchXNG/index.ts index c325f64a1b7b..eebe87adc7f4 100644 --- a/packages/plugins/src/searchXNG/index.ts +++ b/packages/plugins/src/searchXNG/index.ts @@ -48,6 +48,16 @@ const main = async (props: Props, retry = 3): Response => { }); }); + if (results.length === 0) { + return { + result: JSON.stringify([]), + error: { + message: 'No search results', + code: 500 + } + }; + } + return { result: JSON.stringify(results.slice(0, 10)) }; diff --git a/packages/service/common/file/gridfs/controller.ts b/packages/service/common/file/gridfs/controller.ts index a5693e39b403..af304714c6c2 100644 --- a/packages/service/common/file/gridfs/controller.ts +++ b/packages/service/common/file/gridfs/controller.ts @@ -4,7 +4,7 @@ import fsp from 'fs/promises'; import fs from 'fs'; import { DatasetFileSchema } from '@fastgpt/global/core/dataset/type'; import { MongoChatFileSchema, MongoDatasetFileSchema } from './schema'; -import { detectFileEncoding } from '@fastgpt/global/common/file/tools'; +import { detectFileEncoding, detectFileEncodingByPath } from '@fastgpt/global/common/file/tools'; import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { MongoRawTextBuffer } from '../../buffer/rawText/schema'; import { readRawContentByFileBuffer } from '../read/utils'; @@ -36,7 +36,6 @@ export async function uploadFile({ path, filename, contentType, - encoding, metadata = {} }: { bucketName: `${BucketNameEnum}`; @@ -45,7 +44,6 @@ export async function uploadFile({ path: string; filename: string; contentType?: string; - encoding: string; metadata?: Record; }) { if (!path) return Promise.reject(`filePath is empty`); @@ -59,7 +57,7 @@ export async function uploadFile({ // Add default metadata metadata.teamId = teamId; metadata.uid = uid; - metadata.encoding = encoding; + metadata.encoding = await detectFileEncodingByPath(path); // create a gridfs bucket const bucket = getGridBucket(bucketName); diff --git a/packages/service/common/file/image/controller.ts b/packages/service/common/file/image/controller.ts index 3a8e6f2990a2..27bc7ade5c97 100644 --- a/packages/service/common/file/image/controller.ts +++ b/packages/service/common/file/image/controller.ts @@ -1,43 +1,92 @@ import { UploadImgProps } from '@fastgpt/global/common/file/api'; import { imageBaseUrl } from '@fastgpt/global/common/file/image/constants'; import { MongoImage } from './schema'; -import { ClientSession } from '../../../common/mongo'; +import { ClientSession, Types } from '../../../common/mongo'; import { guessBase64ImageType } from '../utils'; import { readFromSecondary } from '../../mongo/utils'; +import { addHours } from 'date-fns'; export const maxImgSize = 1024 * 1024 * 12; const base64MimeRegex = /data:image\/([^\)]+);base64/; export async function uploadMongoImg({ - type, base64Img, teamId, - expiredTime, metadata, - shareId + shareId, + forever = false }: UploadImgProps & { teamId: string; + forever?: Boolean; }) { if (base64Img.length > maxImgSize) { return Promise.reject('Image too large'); } const [base64Mime, base64Data] = base64Img.split(','); + // Check if mime type is valid + if (!base64MimeRegex.test(base64Mime)) { + return Promise.reject('Invalid image mime type'); + } + const mime = `image/${base64Mime.match(base64MimeRegex)?.[1] ?? 'image/jpeg'}`; const binary = Buffer.from(base64Data, 'base64'); const extension = mime.split('/')[1]; const { _id } = await MongoImage.create({ - type, teamId, binary, - expiredTime, metadata: Object.assign({ mime }, metadata), - shareId + shareId, + expiredTime: forever ? undefined : addHours(new Date(), 1) }); return `${process.env.FE_DOMAIN || ''}${process.env.NEXT_PUBLIC_BASE_URL || ''}${imageBaseUrl}${String(_id)}.${extension}`; } +const getIdFromPath = (path?: string) => { + if (!path) return; + + const paths = path.split('/'); + const name = paths[paths.length - 1]; + + if (!name) return; + + const id = name.split('.')[0]; + if (!id || !Types.ObjectId.isValid(id)) return; + + return id; +}; +// 删除旧的头像,新的头像去除过期时间 +export const refreshSourceAvatar = async ( + path?: string, + oldPath?: string, + session?: ClientSession +) => { + const newId = getIdFromPath(path); + const oldId = getIdFromPath(oldPath); + + if (!newId) return; + + await MongoImage.updateOne({ _id: newId }, { $unset: { expiredTime: 1 } }, { session }); + + if (oldId) { + await MongoImage.deleteOne({ _id: oldId }, { session }); + } +}; +export const removeImageByPath = (path?: string, session?: ClientSession) => { + if (!path) return; + + const paths = path.split('/'); + const name = paths[paths.length - 1]; + + if (!name) return; + + const id = name.split('.')[0]; + if (!id || !Types.ObjectId.isValid(id)) return; + + return MongoImage.deleteOne({ _id: id }, { session }); +}; + export async function readMongoImg({ id }: { id: string }) { const formatId = id.replace(/\.[^/.]+$/, ''); diff --git a/packages/service/common/file/image/schema.ts b/packages/service/common/file/image/schema.ts index f5c62d5cd053..1418fa479867 100644 --- a/packages/service/common/file/image/schema.ts +++ b/packages/service/common/file/image/schema.ts @@ -1,8 +1,7 @@ import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant'; -import { connectionMongo, getMongoModel, type Model } from '../../mongo'; +import { connectionMongo, getMongoModel } from '../../mongo'; import { MongoImageSchemaType } from '@fastgpt/global/common/file/image/type.d'; -import { mongoImageTypeMap } from '@fastgpt/global/common/file/image/constants'; -const { Schema, model, models } = connectionMongo; +const { Schema } = connectionMongo; const ImageSchema = new Schema({ teamId: { @@ -14,27 +13,15 @@ const ImageSchema = new Schema({ type: Date, default: () => new Date() }, - expiredTime: { - type: Date - }, - binary: { - type: Buffer - }, - type: { - type: String, - enum: Object.keys(mongoImageTypeMap), - required: true - }, - metadata: { - type: Object - } + expiredTime: Date, + binary: Buffer, + metadata: Object }); try { // tts expired(60 Minutes) ImageSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 60 * 60 }); ImageSchema.index({ type: 1 }); - ImageSchema.index({ createTime: 1 }); // delete related img ImageSchema.index({ teamId: 1, 'metadata.relatedId': 1 }); } catch (error) { diff --git a/packages/service/common/file/read/utils.ts b/packages/service/common/file/read/utils.ts index 0c3cb1ba9f20..7f41f6c47d30 100644 --- a/packages/service/common/file/read/utils.ts +++ b/packages/service/common/file/read/utils.ts @@ -1,5 +1,4 @@ import { uploadMongoImg } from '../image/controller'; -import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants'; import FormData from 'form-data'; import { WorkerNameEnum, runWorker } from '../../../worker/utils'; @@ -8,7 +7,6 @@ import type { ReadFileResponse } from '../../../worker/readFile/type'; import axios from 'axios'; import { addLog } from '../../system/log'; import { batchRun } from '@fastgpt/global/common/fn/utils'; -import { addHours } from 'date-fns'; import { matchMdImgTextAndUpload } from '@fastgpt/global/common/string/markdown'; export type readRawTextByLocalFileParams = { @@ -22,7 +20,7 @@ export const readRawTextByLocalFile = async (params: readRawTextByLocalFileParam const extension = path?.split('.')?.pop()?.toLowerCase() || ''; - const buffer = fs.readFileSync(path); + const buffer = await fs.promises.readFile(path); const { rawText } = await readRawContentByFileBuffer({ extension, @@ -114,10 +112,9 @@ export const readRawContentByFileBuffer = async ({ if (imageList) { await batchRun(imageList, async (item) => { const src = await uploadMongoImg({ - type: MongoImageTypeEnum.collectionImage, base64Img: `data:${item.mime};base64,${item.base64}`, teamId, - expiredTime: addHours(new Date(), 1), + // expiredTime: addHours(new Date(), 1), metadata: { ...metadata, mime: item.mime diff --git a/packages/service/common/middle/reqFrequencyLimit.ts b/packages/service/common/middle/reqFrequencyLimit.ts index 8f950c75dcaa..de7249483aa2 100644 --- a/packages/service/common/middle/reqFrequencyLimit.ts +++ b/packages/service/common/middle/reqFrequencyLimit.ts @@ -9,10 +9,10 @@ import { jsonRes } from '../response'; // unit: times/s // how to use? // export default NextAPI(useQPSLimit(10), handler); // limit 10 times per second for a ip -export function useReqFrequencyLimit(seconds: number, limit: number) { +export function useReqFrequencyLimit(seconds: number, limit: number, force = false) { return async (req: ApiRequestProps, res: NextApiResponse) => { const ip = requestIp.getClientIp(req); - if (!ip || process.env.USE_IP_LIMIT !== 'true') { + if (!ip || (process.env.USE_IP_LIMIT !== 'true' && !force)) { return; } try { @@ -22,10 +22,9 @@ export function useReqFrequencyLimit(seconds: number, limit: number) { expiredTime: addSeconds(new Date(), seconds) }); } catch (_) { - res.status(429); jsonRes(res, { code: 429, - message: ERROR_ENUM.tooManyRequest + error: ERROR_ENUM.tooManyRequest }); } }; diff --git a/packages/service/common/response/index.ts b/packages/service/common/response/index.ts index a6586d0775a8..a6de2e38a92d 100644 --- a/packages/service/common/response/index.ts +++ b/packages/service/common/response/index.ts @@ -33,7 +33,7 @@ export const jsonRes = ( addLog.error(`Api response error: ${url}`, ERROR_RESPONSE[errResponseKey]); - return res.json(ERROR_RESPONSE[errResponseKey]); + return res.status(code).json(ERROR_RESPONSE[errResponseKey]); } // another error diff --git a/packages/service/core/ai/config/embedding/text-embedding-ada-002.json b/packages/service/core/ai/config/embedding/text-embedding-ada-002.json new file mode 100644 index 000000000000..4f6290f7549f --- /dev/null +++ b/packages/service/core/ai/config/embedding/text-embedding-ada-002.json @@ -0,0 +1,11 @@ +{ + "provider": "OpenAI", + "model": "text-embedding-ada-002", + "name": "text-embedding-ada-002", + + "defaultToken": 512, // 默认分块 token + "maxToken": 3000, // 最大分块 token + "weight": 0, // 权重 + + "charsPointsPrice": 0 // 积分/1k token +} diff --git a/packages/service/core/ai/config/llm/gpt-4o-mini.json b/packages/service/core/ai/config/llm/gpt-4o-mini.json new file mode 100644 index 000000000000..0bcf7f6defc2 --- /dev/null +++ b/packages/service/core/ai/config/llm/gpt-4o-mini.json @@ -0,0 +1,33 @@ +{ + "provider": "OpenAI", + "model": "gpt-4o-mini", + "name": "GPT-4o-mini", // alias + + "maxContext": 125000, // 最大上下文 + "maxResponse": 16000, // 最大回复 + "quoteMaxToken": 60000, // 最大引用 + "maxTemperature": 1.2, // 最大温度 + "presencePenaltyRange": [-2, 2], // 惩罚系数范围 + "frequencyPenaltyRange": [-2, 2], // 频率惩罚系数范围 + "responseFormatList": ["text", "json_object", "json_schema"], // 响应格式 + "showStopSign": true, // 是否显示停止符号 + + "vision": true, // 是否支持图片识别 + "toolChoice": true, // 是否支持工具调用 + "functionCall": false, // 是否支持函数调用(一般都可以 false 了,基本不用了) + "defaultSystemChatPrompt": "", // 默认系统提示 + + "datasetProcess": true, // 用于知识库文本处理 + "usedInClassify": true, // 用于问题分类 + "customCQPrompt": "", // 自定义问题分类提示 + "usedInExtractFields": true, // 用于提取字段 + "customExtractPrompt": "", // 自定义提取提示 + "usedInToolCall": true, // 用于工具调用 + "usedInQueryExtension": true, // 用于问题优化 + + "defaultConfig": {}, // 额外的自定义 body + "fieldMap": {}, // body 字段映射 + + "censor": false, // 是否开启敏感词过滤 + "charsPointsPrice": 0 // n 积分/1k token +} diff --git a/packages/service/core/ai/config/rerank/bge-reranker-v2-m3.json b/packages/service/core/ai/config/rerank/bge-reranker-v2-m3.json new file mode 100644 index 000000000000..3cc1a33b5a42 --- /dev/null +++ b/packages/service/core/ai/config/rerank/bge-reranker-v2-m3.json @@ -0,0 +1,6 @@ +{ + "provider": "BAAI", + "model": "bge-reranker-v2-m3", + "name": "bge-reranker-v2-m3", + "charsPointsPrice": 0 +} diff --git a/packages/service/core/ai/config/stt/whisper-1.json b/packages/service/core/ai/config/stt/whisper-1.json new file mode 100644 index 000000000000..2d8639786266 --- /dev/null +++ b/packages/service/core/ai/config/stt/whisper-1.json @@ -0,0 +1,6 @@ +{ + "provider": "OpenAI", + "model": "whisper-1", + "name": "whisper-1", + "charsPointsPrice": 0 +} diff --git a/packages/service/core/ai/config/tts/tts-1.json b/packages/service/core/ai/config/tts/tts-1.json new file mode 100644 index 000000000000..80105c227767 --- /dev/null +++ b/packages/service/core/ai/config/tts/tts-1.json @@ -0,0 +1,32 @@ +{ + "provider": "OpenAI", + "model": "tts-1", + "name": "TTS1", + "charsPointsPrice": 0, + "voices": [ + { + "label": "Alloy", + "value": "alloy" + }, + { + "label": "Echo", + "value": "echo" + }, + { + "label": "Fable", + "value": "fable" + }, + { + "label": "Onyx", + "value": "onyx" + }, + { + "label": "Nova", + "value": "nova" + }, + { + "label": "Shimmer", + "value": "shimmer" + } + ] +} diff --git a/packages/service/core/app/plugin/utils.ts b/packages/service/core/app/plugin/utils.ts index bd1b764da5a9..43e0db8aae8c 100644 --- a/packages/service/core/app/plugin/utils.ts +++ b/packages/service/core/app/plugin/utils.ts @@ -5,11 +5,11 @@ import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; /* Plugin points calculation: - 1. 商业版插件: + 1. 系统插件/商业版插件: - 有错误:返回 0 - - 无错误:返回 配置的点数 + 子节点点数 - 2. 其他插件: - - 返回 子节点点数 + - 无错误:返回 单次积分 + 子流程积分(可配置) + 2. 个人插件 + - 返回 子流程积分 */ export const computedPluginUsage = async ({ plugin, @@ -26,9 +26,9 @@ export const computedPluginUsage = async ({ if (source !== PluginSourceEnum.personal) { if (error) return 0; - const pluginCurrentCose = plugin.currentCost ?? 0; + const pluginCurrentCost = plugin.currentCost ?? 0; - return plugin.hasTokenFee ? pluginCurrentCose + childrenUsages : pluginCurrentCose; + return plugin.hasTokenFee ? pluginCurrentCost + childrenUsages : pluginCurrentCost; } return childrenUsages; diff --git a/packages/service/core/chat/chatItemSchema.ts b/packages/service/core/chat/chatItemSchema.ts index be569b74842a..a18065330308 100644 --- a/packages/service/core/chat/chatItemSchema.ts +++ b/packages/service/core/chat/chatItemSchema.ts @@ -86,24 +86,21 @@ const ChatItemSchema = new Schema({ }); try { - ChatItemSchema.index({ dataId: 1 }, { background: true }); + ChatItemSchema.index({ dataId: 1 }); /* delete by app; delete by chat id; get chat list; get chat logs; close custom feedback; */ - ChatItemSchema.index({ appId: 1, chatId: 1, dataId: 1 }, { background: true }); + ChatItemSchema.index({ appId: 1, chatId: 1, dataId: 1 }); // admin charts - ChatItemSchema.index({ time: -1, obj: 1 }, { background: true }); + ChatItemSchema.index({ time: -1, obj: 1 }); // timer, clear history - ChatItemSchema.index({ teamId: 1, time: -1 }, { background: true }); + ChatItemSchema.index({ teamId: 1, time: -1 }); // Admin charts - ChatItemSchema.index( - { obj: 1, time: -1 }, - { background: true, partialFilterExpression: { obj: 'Human' } } - ); + ChatItemSchema.index({ obj: 1, time: -1 }, { partialFilterExpression: { obj: 'Human' } }); } catch (error) { console.log(error); } diff --git a/packages/service/core/chat/chatSchema.ts b/packages/service/core/chat/chatSchema.ts index 5f5a2f403d31..53a6569ee4ee 100644 --- a/packages/service/core/chat/chatSchema.ts +++ b/packages/service/core/chat/chatSchema.ts @@ -81,19 +81,19 @@ const ChatSchema = new Schema({ }); try { - ChatSchema.index({ chatId: 1 }, { background: true }); + ChatSchema.index({ chatId: 1 }); // get user history - ChatSchema.index({ tmbId: 1, appId: 1, top: -1, updateTime: -1 }, { background: true }); + ChatSchema.index({ tmbId: 1, appId: 1, top: -1, updateTime: -1 }); // delete by appid; clear history; init chat; update chat; auth chat; get chat; - ChatSchema.index({ appId: 1, chatId: 1 }, { background: true }); + ChatSchema.index({ appId: 1, chatId: 1 }); // get chat logs; - ChatSchema.index({ teamId: 1, appId: 1, updateTime: -1 }, { background: true }); + ChatSchema.index({ teamId: 1, appId: 1, updateTime: -1 }); // get share chat history - ChatSchema.index({ shareId: 1, outLinkUid: 1, updateTime: -1 }, { background: true }); + ChatSchema.index({ shareId: 1, outLinkUid: 1, updateTime: -1 }); // timer, clear history - ChatSchema.index({ teamId: 1, updateTime: -1 }, { background: true }); + ChatSchema.index({ teamId: 1, updateTime: -1 }); } catch (error) { console.log(error); } diff --git a/packages/service/core/dataset/collection/controller.ts b/packages/service/core/dataset/collection/controller.ts index 6875fb54648e..061543274989 100644 --- a/packages/service/core/dataset/collection/controller.ts +++ b/packages/service/core/dataset/collection/controller.ts @@ -24,6 +24,7 @@ import { pushDataListToTrainingQueue } from '../training/controller'; import { MongoImage } from '../../../common/file/image/schema'; import { hashStr } from '@fastgpt/global/common/string/tools'; import { addDays } from 'date-fns'; +import { MongoDatasetDataText } from '../data/dataTextSchema'; export const createCollectionAndInsertData = async ({ dataset, @@ -240,12 +241,12 @@ export const delCollectionRelatedSource = async ({ .map((item) => item?.metadata?.relatedImgId || '') .filter(Boolean); - // delete files + // Delete files await delFileByFileIdList({ bucketName: BucketNameEnum.dataset, fileIdList }); - // delete images + // Delete images await delImgByRelatedId({ teamId, relateIds: relatedImageIds, @@ -273,7 +274,7 @@ export async function delCollection({ const datasetIds = Array.from(new Set(collections.map((item) => String(item.datasetId)))); const collectionIds = collections.map((item) => String(item._id)); - // delete training data + // Delete training data await MongoDatasetTraining.deleteMany({ teamId, datasetIds: { $in: datasetIds }, @@ -285,11 +286,16 @@ export async function delCollection({ await delCollectionRelatedSource({ collections, session }); } - // delete dataset.datas + // Delete dataset_datas await MongoDatasetData.deleteMany( { teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } }, { session } ); + // Delete dataset_data_texts + await MongoDatasetDataText.deleteMany( + { teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } }, + { session } + ); // delete collections await MongoDatasetCollection.deleteMany( diff --git a/packages/service/core/dataset/controller.ts b/packages/service/core/dataset/controller.ts index 8ed6a3539d65..96f6523e7e01 100644 --- a/packages/service/core/dataset/controller.ts +++ b/packages/service/core/dataset/controller.ts @@ -6,6 +6,7 @@ import { ClientSession } from '../../common/mongo'; import { MongoDatasetTraining } from './training/schema'; import { MongoDatasetData } from './data/schema'; import { deleteDatasetDataVector } from '../../common/vectorStore/controller'; +import { MongoDatasetDataText } from './data/dataTextSchema'; /* ============= dataset ========== */ /* find all datasetId by top datasetId */ @@ -92,7 +93,7 @@ export async function delDatasetRelevantData({ { session } ).lean(); - // image and file + // Delete Image and file await delCollectionRelatedSource({ collections, session }); // delete collections @@ -101,9 +102,15 @@ export async function delDatasetRelevantData({ datasetId: { $in: datasetIds } }).session(session); - // delete dataset.datas(Not need session) + // No session delete: + // Delete dataset_data_texts + await MongoDatasetDataText.deleteMany({ + teamId, + datasetId: { $in: datasetIds } + }); + // delete dataset_datas await MongoDatasetData.deleteMany({ teamId, datasetId: { $in: datasetIds } }); - // no session delete: delete files, vector data + // Delete vector data await deleteDatasetDataVector({ teamId, datasetIds }); } diff --git a/packages/service/core/dataset/data/dataTextSchema.ts b/packages/service/core/dataset/data/dataTextSchema.ts new file mode 100644 index 000000000000..e0564ed2b61b --- /dev/null +++ b/packages/service/core/dataset/data/dataTextSchema.ts @@ -0,0 +1,45 @@ +import { connectionMongo, getMongoModel } from '../../../common/mongo'; +const { Schema } = connectionMongo; +import { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type.d'; +import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant'; +import { DatasetCollectionName } from '../schema'; +import { DatasetColCollectionName } from '../collection/schema'; +import { DatasetDataCollectionName } from './schema'; + +export const DatasetDataTextCollectionName = 'dataset_data_texts'; + +const DatasetDataTextSchema = new Schema({ + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + datasetId: { + type: Schema.Types.ObjectId, + ref: DatasetCollectionName, + required: true + }, + collectionId: { + type: Schema.Types.ObjectId, + ref: DatasetColCollectionName, + required: true + }, + dataId: { + type: Schema.Types.ObjectId, + ref: DatasetDataCollectionName, + required: true + }, + fullTextToken: String +}); + +try { + DatasetDataTextSchema.index({ teamId: 1, datasetId: 1, fullTextToken: 'text' }); + DatasetDataTextSchema.index({ dataId: 1 }, { unique: true }); +} catch (error) { + console.log(error); +} + +export const MongoDatasetDataText = getMongoModel( + DatasetDataTextCollectionName, + DatasetDataTextSchema +); diff --git a/packages/service/core/dataset/data/schema.ts b/packages/service/core/dataset/data/schema.ts index 241f14285190..85dd8a7d22eb 100644 --- a/packages/service/core/dataset/data/schema.ts +++ b/packages/service/core/dataset/data/schema.ts @@ -1,4 +1,4 @@ -import { connectionMongo, getMongoModel, type Model } from '../../../common/mongo'; +import { connectionMongo, getMongoModel } from '../../../common/mongo'; const { Schema, model, models } = connectionMongo; import { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type.d'; import { @@ -39,10 +39,6 @@ const DatasetDataSchema = new Schema({ type: String, default: '' }, - fullTextToken: { - type: String, - default: '' - }, indexes: { type: [ { @@ -71,17 +67,11 @@ const DatasetDataSchema = new Schema({ type: Number, default: 0 }, - inited: { - type: Boolean - }, - rebuilding: Boolean -}); + rebuilding: Boolean, -DatasetDataSchema.virtual('collection', { - ref: DatasetColCollectionName, - localField: 'collectionId', - foreignField: '_id', - justOne: true + // Abandon + fullTextToken: String, + initFullText: Boolean }); try { @@ -93,13 +83,15 @@ try { chunkIndex: 1, updateTime: -1 }); - // full text index - DatasetDataSchema.index({ teamId: 1, datasetId: 1, fullTextToken: 'text' }); + // FullText tmp full text index + // DatasetDataSchema.index({ teamId: 1, datasetId: 1, fullTextToken: 'text' }); // Recall vectors after data matching DatasetDataSchema.index({ teamId: 1, datasetId: 1, collectionId: 1, 'indexes.dataId': 1 }); DatasetDataSchema.index({ updateTime: 1 }); // rebuild data DatasetDataSchema.index({ rebuilding: 1, teamId: 1, datasetId: 1 }); + + DatasetDataSchema.index({ initFullText: 1 }); } catch (error) { console.log(error); } diff --git a/packages/service/core/dataset/search/controller.ts b/packages/service/core/dataset/search/controller.ts index 4b87d2ca7fe7..0de4c2884d3b 100644 --- a/packages/service/core/dataset/search/controller.ts +++ b/packages/service/core/dataset/search/controller.ts @@ -8,8 +8,8 @@ import { getVectorsByText } from '../../ai/embedding'; import { getVectorModel } from '../../ai/model'; import { MongoDatasetData } from '../data/schema'; import { - DatasetCollectionSchemaType, DatasetDataSchemaType, + DatasetDataTextSchemaType, SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type'; import { MongoDatasetCollection } from '../collection/schema'; @@ -23,6 +23,7 @@ import { Types } from '../../../common/mongo'; import json5 from 'json5'; import { MongoDatasetCollectionTags } from '../tag/schema'; import { readFromSecondary } from '../../../common/mongo/utils'; +import { MongoDatasetDataText } from '../data/dataTextSchema'; type SearchDatasetDataProps = { teamId: string; @@ -266,57 +267,60 @@ export async function searchDatasetData(props: SearchDatasetDataProps) { filterCollectionIdList }); - // get q and a - const dataList = await MongoDatasetData.find( - { - teamId, - datasetId: { $in: datasetIds }, - collectionId: { $in: Array.from(new Set(results.map((item) => item.collectionId))) }, - 'indexes.dataId': { $in: results.map((item) => item.id?.trim()) } - }, - 'datasetId collectionId updateTime q a chunkIndex indexes' - ) - .populate<{ collection: DatasetCollectionSchemaType }>( - 'collection', - 'name fileId rawLink externalFileId externalFileUrl' - ) - .lean(); - - // add score to data(It's already sorted. The first one is the one with the most points) - const concatResults = dataList.map((data) => { - const dataIdList = data.indexes.map((item) => item.dataId); - - const maxScoreResult = results.find((item) => { - return dataIdList.includes(item.id); - }); - - return { - ...data, - score: maxScoreResult?.score || 0 - }; - }); + // Get data and collections + const collectionIdList = Array.from(new Set(results.map((item) => item.collectionId))); + const [dataList, collections] = await Promise.all([ + MongoDatasetData.find( + { + teamId, + datasetId: { $in: datasetIds }, + collectionId: { $in: collectionIdList }, + 'indexes.dataId': { $in: results.map((item) => item.id?.trim()) } + }, + '_id datasetId collectionId updateTime q a chunkIndex indexes', + { ...readFromSecondary } + ).lean(), + MongoDatasetCollection.find( + { + _id: { $in: collectionIdList } + }, + '_id name fileId rawLink externalFileId externalFileUrl', + { ...readFromSecondary } + ).lean() + ]); - concatResults.sort((a, b) => b.score - a.score); + const formatResult = results + .map((item, index) => { + const collection = collections.find((col) => String(col._id) === String(item.collectionId)); + if (!collection) { + console.log('Collection is not found', item); + return; + } + const data = dataList.find((data) => + data.indexes.some((index) => index.dataId === item.id) + ); + if (!data) { + console.log('Data is not found', item); + return; + } - const formatResult = concatResults.map((data, index) => { - if (!data.collectionId) { - console.log('Collection is not found', data); - } + const score = item?.score || 0; - const result: SearchDataResponseItemType = { - id: String(data._id), - updateTime: data.updateTime, - q: data.q, - a: data.a, - chunkIndex: data.chunkIndex, - datasetId: String(data.datasetId), - collectionId: String(data.collectionId), - ...getCollectionSourceData(data.collection), - score: [{ type: SearchScoreTypeEnum.embedding, value: data.score, index }] - }; + const result: SearchDataResponseItemType = { + id: String(data._id), + updateTime: data.updateTime, + q: data.q, + a: data.a, + chunkIndex: data.chunkIndex, + datasetId: String(data.datasetId), + collectionId: String(data.collectionId), + ...getCollectionSourceData(collection), + score: [{ type: SearchScoreTypeEnum.embedding, value: score, index }] + }; - return result; - }); + return result; + }) + .filter(Boolean) as SearchDataResponseItemType[]; return { embeddingRecallResults: formatResult, @@ -344,88 +348,224 @@ export async function searchDatasetData(props: SearchDatasetDataProps) { }; } - let searchResults = ( + const searchResults = ( await Promise.all( datasetIds.map(async (id) => { - return MongoDatasetData.aggregate([ - { - $match: { - teamId: new Types.ObjectId(teamId), - datasetId: new Types.ObjectId(id), - $text: { $search: jiebaSplit({ text: query }) }, - ...(filterCollectionIdList - ? { - collectionId: { - $in: filterCollectionIdList.map((id) => new Types.ObjectId(id)) + return MongoDatasetData.aggregate( + [ + { + $match: { + teamId: new Types.ObjectId(teamId), + datasetId: new Types.ObjectId(id), + $text: { $search: jiebaSplit({ text: query }) }, + ...(filterCollectionIdList + ? { + collectionId: { + $in: filterCollectionIdList.map((id) => new Types.ObjectId(id)) + } } - } - : {}), - ...(forbidCollectionIdList && forbidCollectionIdList.length > 0 - ? { - collectionId: { - $nin: forbidCollectionIdList.map((id) => new Types.ObjectId(id)) + : {}), + ...(forbidCollectionIdList && forbidCollectionIdList.length > 0 + ? { + collectionId: { + $nin: forbidCollectionIdList.map((id) => new Types.ObjectId(id)) + } } - } - : {}) - } - }, - { - $addFields: { - score: { $meta: 'textScore' } + : {}) + } + }, + { + $sort: { + score: { $meta: 'textScore' } + } + }, + { + $limit: limit + }, + { + $project: { + _id: 1, + datasetId: 1, + collectionId: 1, + updateTime: 1, + q: 1, + a: 1, + chunkIndex: 1, + score: { $meta: 'textScore' } + } } - }, - { - $sort: { - score: { $meta: 'textScore' } - } - }, - { - $limit: limit - }, + ], { - $project: { - _id: 1, - datasetId: 1, - collectionId: 1, - updateTime: 1, - q: 1, - a: 1, - chunkIndex: 1, - score: 1 - } + ...readFromSecondary } - ]); + ); }) ) ).flat() as (DatasetDataSchemaType & { score: number })[]; - // resort - searchResults.sort((a, b) => b.score - a.score); - searchResults.slice(0, limit); - + // Get data and collections const collections = await MongoDatasetCollection.find( { _id: { $in: searchResults.map((item) => item.collectionId) } }, - '_id name fileId rawLink' - ); + '_id name fileId rawLink externalFileId externalFileUrl', + { ...readFromSecondary } + ).lean(); return { - fullTextRecallResults: searchResults.map((item, index) => { - const collection = collections.find((col) => String(col._id) === String(item.collectionId)); - return { - id: String(item._id), - datasetId: String(item.datasetId), - collectionId: String(item.collectionId), - updateTime: item.updateTime, - ...getCollectionSourceData(collection), - q: item.q, - a: item.a, - chunkIndex: item.chunkIndex, - indexes: item.indexes, - score: [{ type: SearchScoreTypeEnum.fullText, value: item.score, index }] - }; - }), + fullTextRecallResults: searchResults + .map((data, index) => { + const collection = collections.find( + (col) => String(col._id) === String(data.collectionId) + ); + if (!collection) { + console.log('Collection is not found', data); + return; + } + + return { + id: String(data._id), + datasetId: String(data.datasetId), + collectionId: String(data.collectionId), + updateTime: data.updateTime, + q: data.q, + a: data.a, + chunkIndex: data.chunkIndex, + indexes: data.indexes, + ...getCollectionSourceData(collection), + score: [{ type: SearchScoreTypeEnum.fullText, value: data.score ?? 0, index }] + }; + }) + .filter(Boolean) as SearchDataResponseItemType[], + tokenLen: 0 + }; + }; + const fullTextRecall2 = async ({ + query, + limit, + filterCollectionIdList, + forbidCollectionIdList + }: { + query: string; + limit: number; + filterCollectionIdList?: string[]; + forbidCollectionIdList: string[]; + }): Promise<{ + fullTextRecallResults: SearchDataResponseItemType[]; + tokenLen: number; + }> => { + if (limit === 0) { + return { + fullTextRecallResults: [], + tokenLen: 0 + }; + } + + const searchResults = ( + await Promise.all( + datasetIds.map(async (id) => { + return MongoDatasetDataText.aggregate( + [ + { + $match: { + teamId: new Types.ObjectId(teamId), + datasetId: new Types.ObjectId(id), + $text: { $search: jiebaSplit({ text: query }) }, + ...(filterCollectionIdList + ? { + collectionId: { + $in: filterCollectionIdList.map((id) => new Types.ObjectId(id)) + } + } + : {}), + ...(forbidCollectionIdList && forbidCollectionIdList.length > 0 + ? { + collectionId: { + $nin: forbidCollectionIdList.map((id) => new Types.ObjectId(id)) + } + } + : {}) + } + }, + { + $sort: { + score: { $meta: 'textScore' } + } + }, + { + $limit: limit + }, + { + $project: { + _id: 1, + collectionId: 1, + dataId: 1, + score: { $meta: 'textScore' } + } + } + ], + { + ...readFromSecondary + } + ); + }) + ) + ).flat() as (DatasetDataTextSchemaType & { score: number })[]; + + // Get data and collections + const [dataList, collections] = await Promise.all([ + MongoDatasetData.find( + { + _id: { $in: searchResults.map((item) => item.dataId) } + }, + '_id datasetId collectionId updateTime q a chunkIndex indexes', + { ...readFromSecondary } + ).lean(), + MongoDatasetCollection.find( + { + _id: { $in: searchResults.map((item) => item.collectionId) } + }, + '_id name fileId rawLink externalFileId externalFileUrl', + { ...readFromSecondary } + ).lean() + ]); + + return { + fullTextRecallResults: searchResults + .map((item, index) => { + const collection = collections.find( + (col) => String(col._id) === String(item.collectionId) + ); + if (!collection) { + console.log('Collection is not found', item); + return; + } + const data = dataList.find((data) => String(data._id) === String(item.dataId)); + if (!data) { + console.log('Data is not found', item); + return; + } + + return { + id: String(data._id), + datasetId: String(data.datasetId), + collectionId: String(data.collectionId), + updateTime: data.updateTime, + q: data.q, + a: data.a, + chunkIndex: data.chunkIndex, + indexes: data.indexes, + ...getCollectionSourceData(collection), + score: [ + { + type: SearchScoreTypeEnum.fullText, + value: item.score || 0, + index + } + ] + }; + }) + .filter(Boolean) as SearchDataResponseItemType[], tokenLen: 0 }; }; @@ -496,7 +636,8 @@ export async function searchDatasetData(props: SearchDatasetDataProps) { forbidCollectionIdList, filterCollectionIdList }), - fullTextRecall({ + // FullText tmp + fullTextRecall2({ query, limit: fullTextLimit, filterCollectionIdList, diff --git a/packages/service/support/outLink/schema.ts b/packages/service/support/outLink/schema.ts index 7d1f1dbe333c..0c6d5ea4c358 100644 --- a/packages/service/support/outLink/schema.ts +++ b/packages/service/support/outLink/schema.ts @@ -92,6 +92,7 @@ OutLinkSchema.virtual('associatedApp', { try { OutLinkSchema.index({ shareId: -1 }); + OutLinkSchema.index({ teamId: 1, tmbId: 1, appId: 1 }); } catch (error) { console.log(error); } diff --git a/packages/service/support/permission/auth/org.ts b/packages/service/support/permission/auth/org.ts new file mode 100644 index 000000000000..d864b1cb5f3d --- /dev/null +++ b/packages/service/support/permission/auth/org.ts @@ -0,0 +1,43 @@ +import { TeamPermission } from '@fastgpt/global/support/permission/user/controller'; +import { AuthModeType, AuthResponseType } from '../type'; +import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; +import { authUserPer } from '../user/auth'; +import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant'; + +/* + Team manager can control org +*/ +export const authOrgMember = async ({ + orgIds, + ...props +}: { + orgIds: string | string[]; +} & AuthModeType): Promise => { + const result = await authUserPer({ + ...props, + per: ManagePermissionVal + }); + const { teamId, tmbId, isRoot, tmb } = result; + + if (isRoot) { + return { + teamId, + tmbId, + userId: result.userId, + appId: result.appId, + apikey: result.apikey, + isRoot, + authType: result.authType, + permission: new TeamPermission({ isOwner: true }) + }; + } + + if (tmb.permission.hasManagePer) { + return { + ...result, + permission: tmb.permission + }; + } + + return Promise.reject(TeamErrEnum.unAuthTeam); +}; diff --git a/packages/service/support/permission/auth/team.ts b/packages/service/support/permission/auth/team.ts index b659f2ffdfb8..48d676ed4f1f 100644 --- a/packages/service/support/permission/auth/team.ts +++ b/packages/service/support/permission/auth/team.ts @@ -1,8 +1,8 @@ import { MongoTeamMember } from '../../user/team/teamMemberSchema'; import { checkTeamAIPoints } from '../teamLimit'; -import { UserErrEnum } from '@fastgpt/global/common/error/code/user'; import { UserModelSchema } from '@fastgpt/global/support/user/type'; import { TeamSchema } from '@fastgpt/global/support/user/team/type'; +import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; export async function getUserChatInfoAndAuthTeamPoints(tmbId: string) { const tmb = await MongoTeamMember.findById(tmbId, 'userId teamId') @@ -18,7 +18,7 @@ export async function getUserChatInfoAndAuthTeamPoints(tmbId: string) { ]) .lean(); - if (!tmb) return Promise.reject(UserErrEnum.unAuthUser); + if (!tmb) return Promise.reject(TeamErrEnum.notUser); await checkTeamAIPoints(tmb.team._id); diff --git a/packages/service/support/permission/controller.ts b/packages/service/support/permission/controller.ts index 458c05d019f8..f233c66b19d0 100644 --- a/packages/service/support/permission/controller.ts +++ b/packages/service/support/permission/controller.ts @@ -8,10 +8,7 @@ import { authOpenApiKey } from '../openapi/auth'; import { FileTokenQuery } from '@fastgpt/global/common/file/type'; import { MongoResourcePermission } from './schema'; import { ClientSession } from 'mongoose'; -import { - PermissionValueType, - ResourcePermissionType -} from '@fastgpt/global/support/permission/type'; +import { PermissionValueType } from '@fastgpt/global/support/permission/type'; import { bucketNameMap } from '@fastgpt/global/common/file/constants'; import { addMinutes } from 'date-fns'; import { getGroupsByTmbId } from './memberGroup/controllers'; @@ -21,6 +18,8 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type'; import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type'; import { UserModelSchema } from '@fastgpt/global/support/user/type'; +import { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type'; +import { getOrgIdSetWithParentByTmbId } from './org/controllers'; /** get resource permission for a team member * If there is no permission for the team member, it will return undefined @@ -67,67 +66,44 @@ export const getResourcePermission = async ({ } // If there is no personal permission, get the group permission - const groupIdList = (await getGroupsByTmbId({ tmbId, teamId })).map((item) => item._id); - - if (groupIdList.length === 0) { - return undefined; - } - - // get the maximum permission of the group - const pers = ( - await MongoResourcePermission.find( - { - teamId, - resourceType, - groupId: { - $in: groupIdList - }, - resourceId - }, - 'permission' - ).lean() - ).map((item) => item.permission); - - const groupPer = getGroupPer(pers); + const [groupPers, orgPers] = await Promise.all([ + getGroupsByTmbId({ tmbId, teamId }) + .then((res) => res.map((item) => item._id)) + .then((groupIdList) => + MongoResourcePermission.find( + { + teamId, + resourceType, + groupId: { + $in: groupIdList + }, + resourceId + }, + 'permission' + ).lean() + ) + .then((perList) => perList.map((item) => item.permission)), + getOrgIdSetWithParentByTmbId({ tmbId, teamId }) + .then((item) => Array.from(item)) + .then((orgIds) => + MongoResourcePermission.find( + { + teamId, + resourceType, + orgId: { + $in: Array.from(orgIds) + }, + resourceId + }, + 'permission' + ).lean() + ) + .then((perList) => perList.map((item) => item.permission)) + ]); - return groupPer; + return concatPer([...groupPers, ...orgPers]); }; -/* 仅取 members 不取 groups */ -export async function getResourceAllClbs({ - resourceId, - teamId, - resourceType, - session -}: { - teamId: string; - session?: ClientSession; -} & ( - | { - resourceType: 'team'; - resourceId?: undefined; - } - | { - resourceType: Omit; - resourceId?: string | null; - } -)): Promise { - return MongoResourcePermission.find( - { - resourceType: resourceType, - teamId: teamId, - resourceId, - groupId: { - $exists: false - } - }, - null, - { - session - } - ).lean(); -} - export async function getResourceClbsAndGroups({ resourceId, resourceType, @@ -155,10 +131,17 @@ export const getClbsAndGroupsWithInfo = async ({ resourceType, teamId }: { - resourceId: ParentIdType; - resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>; teamId: string; -}) => +} & ( + | { + resourceId: ParentIdType; + resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>; + } + | { + resourceType: 'team'; + resourceId?: undefined; + } +)) => Promise.all([ MongoResourcePermission.find({ teamId, @@ -170,7 +153,7 @@ export const getClbsAndGroupsWithInfo = async ({ }) .populate<{ tmb: TeamMemberSchema & { user: UserModelSchema } }>({ path: 'tmb', - select: 'name userId', + select: 'name userId role', populate: { path: 'user', select: 'avatar' @@ -186,6 +169,16 @@ export const getClbsAndGroupsWithInfo = async ({ } }) .populate<{ group: MemberGroupSchemaType }>('group', 'name avatar') + .lean(), + MongoResourcePermission.find({ + teamId, + resourceId, + resourceType, + orgId: { + $exists: true + } + }) + .populate<{ org: OrgSchemaType }>({ path: 'org', select: 'name avatar' }) .lean() ]); @@ -196,6 +189,7 @@ export const delResourcePermission = ({ session, tmbId, groupId, + orgId, ...props }: { resourceType: PerResourceTypeEnum; @@ -204,15 +198,18 @@ export const delResourcePermission = ({ session?: ClientSession; tmbId?: string; groupId?: string; + orgId?: string; }) => { - // tmbId or groupId only one and not both - if (!!tmbId === !!groupId) { + // either tmbId or groupId or orgId must be provided + if (!tmbId && !groupId && !orgId) { return Promise.reject(CommonErrEnum.missingParams); } + return MongoResourcePermission.deleteOne( { ...(tmbId ? { tmbId } : {}), ...(groupId ? { groupId } : {}), + ...(orgId ? { orgId } : {}), ...props }, { session } @@ -250,7 +247,7 @@ export function authJWT(token: string) { }>((resolve, reject) => { const key = process.env.TOKEN_KEY as string; - jwt.verify(token, key, function (err, decoded: any) { + jwt.verify(token, key, (err, decoded: any) => { if (err || !decoded?.userId) { reject(ERROR_ENUM.unAuthorization); return; @@ -436,7 +433,7 @@ export const authFileToken = (token?: string) => } const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken'; - jwt.verify(token, key, function (err, decoded: any) { + jwt.verify(token, key, (err, decoded: any) => { if (err || !decoded.bucketName || !decoded?.teamId || !decoded?.fileId) { reject(ERROR_ENUM.unAuthFile); return; @@ -450,10 +447,10 @@ export const authFileToken = (token?: string) => }); }); -export const getGroupPer = (groups: PermissionValueType[] = []) => { - if (groups.length === 0) { +export const concatPer = (perList: PermissionValueType[] = []) => { + if (perList.length === 0) { return undefined; } - return new Permission().addPer(...groups).value; + return new Permission().addPer(...perList).value; }; diff --git a/packages/service/support/permission/inheritPermission.ts b/packages/service/support/permission/inheritPermission.ts index c3ac42eb8a3f..4f9993f5a9f3 100644 --- a/packages/service/support/permission/inheritPermission.ts +++ b/packages/service/support/permission/inheritPermission.ts @@ -1,11 +1,11 @@ import { mongoSessionRun } from '../../common/mongo/sessionRun'; import { MongoResourcePermission } from './schema'; -import { ClientSession, Model } from 'mongoose'; -import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; -import { PermissionValueType } from '@fastgpt/global/support/permission/type'; +import type { ClientSession, Model } from 'mongoose'; +import type { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; +import type { PermissionValueType } from '@fastgpt/global/support/permission/type'; import { getResourceClbsAndGroups } from './controller'; -import { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; -import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; +import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; +import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; export type SyncChildrenPermissionResourceType = { _id: string; @@ -18,6 +18,7 @@ export type UpdateCollaboratorItem = { } & RequireOnlyOne<{ tmbId: string; groupId: string; + orgId: string; }>; // sync the permission to all children folders. @@ -161,7 +162,7 @@ export async function resumeInheritPermission({ } } -/* +/* Delete all the collaborators and then insert the new collaborators. */ export async function syncCollaborators({ diff --git a/packages/service/support/permission/memberGroup/controllers.ts b/packages/service/support/permission/memberGroup/controllers.ts index 78bae10c910d..c34d2b4b31b5 100644 --- a/packages/service/support/permission/memberGroup/controllers.ts +++ b/packages/service/support/permission/memberGroup/controllers.ts @@ -1,9 +1,6 @@ import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type'; import { MongoGroupMemberModel } from './groupMemberSchema'; -import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type'; -import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; -import { MongoResourcePermission } from '../schema'; -import { getGroupPer, parseHeaderCert } from '../controller'; +import { parseHeaderCert } from '../controller'; import { MongoMemberGroupModel } from './memberGroupSchema'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import { ClientSession } from 'mongoose'; @@ -50,26 +47,32 @@ export const getTeamDefaultGroup = async ({ export const getGroupsByTmbId = async ({ tmbId, teamId, - role + role, + session }: { tmbId: string; teamId: string; role?: `${GroupMemberRole}`[]; + session?: ClientSession; }) => ( await Promise.all([ ( - await MongoGroupMemberModel.find({ - tmbId, - groupId: { - $exists: true + await MongoGroupMemberModel.find( + { + tmbId, + groupId: { + $exists: true + }, + ...(role ? { role: { $in: role } } : {}) }, - ...(role ? { role: { $in: role } } : {}) - }) + undefined, + { session } + ) .populate<{ group: MemberGroupSchemaType }>('group') .lean() ).map((item) => item.group), - role ? [] : getTeamDefaultGroup({ teamId }) + role ? [] : getTeamDefaultGroup({ teamId, session }) ]) ).flat(); @@ -79,46 +82,6 @@ export const getGroupMembersByGroupId = async (groupId: string) => { }).lean(); }; -/** - * Get tmb's group permission: the maximum permission of the group - * @param tmbId - * @param resourceId - * @param resourceType - * @returns the maximum permission of the group - */ -export const getGroupPermission = async ({ - tmbId, - resourceId, - teamId, - resourceType -}: { - tmbId: string; - teamId: string; -} & ( - | { - resourceId?: undefined; - resourceType: 'team'; - } - | { - resourceId: string; - resourceType: Omit; - } -)) => { - const groupIds = (await getGroupsByTmbId({ tmbId, teamId })).map((item) => item._id); - const groupPermissions = ( - await MongoResourcePermission.find({ - groupId: { - $in: groupIds - }, - resourceType, - resourceId, - teamId - }) - ).map((item) => item.permission); - - return getGroupPer(groupPermissions); -}; - // auth group member role export const authGroupMemberRole = async ({ groupId, @@ -140,8 +103,12 @@ export const authGroupMemberRole = async ({ tmbId }; } - const groupMember = await MongoGroupMemberModel.findOne({ groupId, tmbId }); - const tmb = await getTmbInfoByTmbId({ tmbId }); + const [groupMember, tmb] = await Promise.all([ + MongoGroupMemberModel.findOne({ groupId, tmbId }), + getTmbInfoByTmbId({ tmbId }) + ]); + + // Team admin or role check if (tmb.permission.hasManagePer || (groupMember && role.includes(groupMember.role))) { return { ...result, diff --git a/packages/service/support/permission/org/controllers.ts b/packages/service/support/permission/org/controllers.ts new file mode 100644 index 000000000000..ce0746055285 --- /dev/null +++ b/packages/service/support/permission/org/controllers.ts @@ -0,0 +1,95 @@ +import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; +import type { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type'; +import type { ClientSession } from 'mongoose'; +import { MongoOrgModel } from './orgSchema'; +import { MongoOrgMemberModel } from './orgMemberSchema'; +import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant'; + +export const getOrgsByTmbId = async ({ teamId, tmbId }: { teamId: string; tmbId: string }) => + MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId').lean(); + +export const getOrgIdSetWithParentByTmbId = async ({ + teamId, + tmbId +}: { + teamId: string; + tmbId: string; +}) => { + const orgMembers = await MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId').lean(); + + const orgIds = Array.from(new Set(orgMembers.map((item) => String(item.orgId)))); + const orgs = await MongoOrgModel.find({ _id: { $in: orgIds } }, 'path').lean(); + + const pathIdList = new Set( + orgs + .map((org) => { + const pathIdList = org.path.split('/').filter(Boolean); + return pathIdList; + }) + .flat() + ); + const parentOrgs = await MongoOrgModel.find( + { + teamId, + pathId: { $in: Array.from(pathIdList) } + }, + '_id' + ).lean(); + const parentOrgIds = parentOrgs.map((item) => String(item._id)); + + return new Set([...orgIds, ...parentOrgIds]); +}; + +export const getChildrenByOrg = async ({ + org, + teamId, + session +}: { + org: OrgSchemaType; + teamId: string; + session?: ClientSession; +}) => { + return MongoOrgModel.find( + { teamId, path: { $regex: `^${getOrgChildrenPath(org)}` } }, + undefined, + { + session + } + ).lean(); +}; + +export const getOrgAndChildren = async ({ + orgId, + teamId, + session +}: { + orgId: string; + teamId: string; + session?: ClientSession; +}) => { + const org = await MongoOrgModel.findOne({ _id: orgId, teamId }, undefined, { session }).lean(); + if (!org) { + return Promise.reject(TeamErrEnum.orgNotExist); + } + const children = await getChildrenByOrg({ org, teamId, session }); + return { org, children }; +}; + +export async function createRootOrg({ + teamId, + session +}: { + teamId: string; + session?: ClientSession; +}) { + return MongoOrgModel.create( + [ + { + teamId, + name: 'ROOT', + path: '' + } + ], + { session } + ); +} diff --git a/packages/service/support/permission/org/orgMemberSchema.ts b/packages/service/support/permission/org/orgMemberSchema.ts new file mode 100644 index 000000000000..3abc5c233ed9 --- /dev/null +++ b/packages/service/support/permission/org/orgMemberSchema.ts @@ -0,0 +1,65 @@ +import { OrgCollectionName } from '@fastgpt/global/support/user/team/org/constant'; +import { connectionMongo, getMongoModel } from '../../../common/mongo'; +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; +import { OrgMemberSchemaType } from '@fastgpt/global/support/user/team/org/type'; +const { Schema } = connectionMongo; + +export const OrgMemberCollectionName = 'team_org_members'; + +export const OrgMemberSchema = new Schema({ + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + orgId: { + type: Schema.Types.ObjectId, + ref: OrgCollectionName, + required: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, + required: true + } + // role: { + // type: String, + // enum: Object.values(OrgMemberRole), + // required: true, + // default: OrgMemberRole.member + // } +}); + +OrgMemberSchema.virtual('org', { + ref: OrgCollectionName, + localField: 'orgId', + foreignField: '_id', + justOne: true +}); + +try { + OrgMemberSchema.index( + { + teamId: 1, + orgId: 1, + tmbId: 1 + }, + { + unique: true + } + ); + OrgMemberSchema.index({ + teamId: 1, + tmbId: 1 + }); +} catch (error) { + console.log(error); +} + +export const MongoOrgMemberModel = getMongoModel( + OrgMemberCollectionName, + OrgMemberSchema +); diff --git a/packages/service/support/permission/org/orgSchema.ts b/packages/service/support/permission/org/orgSchema.ts new file mode 100644 index 000000000000..45030c4a664b --- /dev/null +++ b/packages/service/support/permission/org/orgSchema.ts @@ -0,0 +1,77 @@ +import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant'; +import { OrgCollectionName } from '@fastgpt/global/support/user/team/org/constant'; +import type { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type'; +import { connectionMongo, getMongoModel } from '../../../common/mongo'; +import { OrgMemberCollectionName } from './orgMemberSchema'; +import { getNanoid } from '@fastgpt/global/common/string/tools'; +const { Schema } = connectionMongo; + +export const OrgSchema = new Schema( + { + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + pathId: { + // path id, only used for path + type: String, + required: true, + default: () => getNanoid() + }, + path: { + type: String, + required: function (this: OrgSchemaType) { + return typeof this.path !== 'string'; + } // allow empty string, but not null + }, + name: { + type: String, + required: true + }, + avatar: String, + description: String, + updateTime: { + type: Date, + default: () => new Date() + } + }, + { + // Auto update updateTime + timestamps: { + updatedAt: 'updateTime' + } + } +); + +OrgSchema.virtual('members', { + ref: OrgMemberCollectionName, + localField: '_id', + foreignField: 'orgId' +}); +// OrgSchema.virtual('permission', { +// ref: ResourcePermissionCollectionName, +// localField: '_id', +// foreignField: 'orgId', +// justOne: true +// }); + +try { + OrgSchema.index({ + teamId: 1, + path: 1 + }); + OrgSchema.index( + { + teamId: 1, + pathId: 1 + }, + { + unique: true + } + ); +} catch (error) { + console.log(error); +} + +export const MongoOrgModel = getMongoModel(OrgCollectionName, OrgSchema); diff --git a/packages/service/support/permission/schema.ts b/packages/service/support/permission/schema.ts index ff5491d65e31..10b4a0cdb846 100644 --- a/packages/service/support/permission/schema.ts +++ b/packages/service/support/permission/schema.ts @@ -6,6 +6,7 @@ import { connectionMongo, getMongoModel } from '../../common/mongo'; import type { ResourcePermissionType } from '@fastgpt/global/support/permission/type'; import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; import { MemberGroupCollectionName } from './memberGroup/memberGroupSchema'; +import { OrgCollectionName } from '@fastgpt/global/support/user/team/org/constant'; const { Schema } = connectionMongo; export const ResourcePermissionCollectionName = 'resource_permissions'; @@ -23,6 +24,10 @@ export const ResourcePermissionSchema = new Schema({ type: Schema.Types.ObjectId, ref: MemberGroupCollectionName }, + orgId: { + type: Schema.Types.ObjectId, + ref: OrgCollectionName + }, resourceType: { type: String, enum: Object.values(PerResourceTypeEnum), @@ -51,6 +56,12 @@ ResourcePermissionSchema.virtual('group', { foreignField: '_id', justOne: true }); +ResourcePermissionSchema.virtual('org', { + ref: OrgCollectionName, + localField: 'orgId', + foreignField: '_id', + justOne: true +}); try { ResourcePermissionSchema.index( @@ -70,6 +81,23 @@ try { } ); + ResourcePermissionSchema.index( + { + resourceType: 1, + teamId: 1, + resourceId: 1, + orgId: 1 + }, + { + unique: true, + partialFilterExpression: { + orgId: { + $exists: true + } + } + } + ); + ResourcePermissionSchema.index( { resourceType: 1, @@ -87,6 +115,7 @@ try { } ); + // Delete tmb permission ResourcePermissionSchema.index({ resourceType: 1, teamId: 1, diff --git a/packages/service/support/permission/teamLimit.ts b/packages/service/support/permission/teamLimit.ts index 814480ccf549..1a383f85b894 100644 --- a/packages/service/support/permission/teamLimit.ts +++ b/packages/service/support/permission/teamLimit.ts @@ -19,9 +19,7 @@ export const checkDatasetLimit = async ({ if (!standardConstants) return; if (usedDatasetSize + insertLen >= datasetMaxSize) { - return Promise.reject( - `您的知识库容量为: ${datasetMaxSize}组,已使用: ${usedDatasetSize}组,导入当前文件需要: ${insertLen}组,请增加知识库容量后导入。` - ); + return Promise.reject(TeamErrEnum.datasetSizeNotEnough); } if (usedPoints >= totalPoints) { diff --git a/packages/service/support/user/schema.ts b/packages/service/support/user/schema.ts index 89b5eb58d53c..10094ee6fbbb 100644 --- a/packages/service/support/user/schema.ts +++ b/packages/service/support/user/schema.ts @@ -3,22 +3,10 @@ const { Schema } = connectionMongo; import { hashStr } from '@fastgpt/global/common/string/tools'; import type { UserModelSchema } from '@fastgpt/global/support/user/type'; import { UserStatusEnum, userStatusMap } from '@fastgpt/global/support/user/constant'; +import { getRandomUserAvatar } from '@fastgpt/global/support/user/utils'; export const userCollectionName = 'users'; -const defaultAvatars = [ - '/imgs/avatar/RoyalBlueAvatar.svg', - '/imgs/avatar/PurpleAvatar.svg', - '/imgs/avatar/AdoraAvatar.svg', - '/imgs/avatar/OrangeAvatar.svg', - '/imgs/avatar/RedAvatar.svg', - '/imgs/avatar/GrayModernAvatar.svg', - '/imgs/avatar/TealAvatar.svg', - '/imgs/avatar/GreenAvatar.svg', - '/imgs/avatar/BrightBlueAvatar.svg', - '/imgs/avatar/BlueAvatar.svg' -]; - const UserSchema = new Schema({ status: { type: String, @@ -47,7 +35,7 @@ const UserSchema = new Schema({ }, avatar: { type: String, - default: defaultAvatars[Math.floor(Math.random() * defaultAvatars.length)] + default: () => getRandomUserAvatar() }, promotionRate: { @@ -78,11 +66,8 @@ const UserSchema = new Schema({ }); try { - // login - UserSchema.index({ username: 1, password: 1 }, { background: true }); - // Admin charts - UserSchema.index({ createTime: -1 }, { background: true }); + UserSchema.index({ createTime: -1 }); } catch (error) { console.log(error); } diff --git a/packages/service/support/user/team/controller.ts b/packages/service/support/user/team/controller.ts index ede448c93b6d..451e02f5b680 100644 --- a/packages/service/support/user/team/controller.ts +++ b/packages/service/support/user/team/controller.ts @@ -16,6 +16,8 @@ import { MongoMemberGroupModel } from '../../permission/memberGroup/memberGroupS import { mongoSessionRun } from '../../../common/mongo/sessionRun'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import { getAIApi, openaiBaseUrl } from '../../../core/ai/config'; +import { createRootOrg } from '../../permission/org/controllers'; +import { refreshSourceAvatar } from '../../../common/file/image/controller'; async function getTeamMember(match: Record): Promise { const tmb = await MongoTeamMember.findOne(match).populate<{ team: TeamSchema }>('team').lean(); @@ -32,6 +34,7 @@ async function getTeamMember(match: Record): Promise Date.now() }, - balance: { - type: Number, - default: 0 - }, + balance: Number, teamDomain: { type: String }, diff --git a/packages/service/support/wallet/usage/schema.ts b/packages/service/support/wallet/usage/schema.ts index fc1a3b500b48..7416d6b45725 100644 --- a/packages/service/support/wallet/usage/schema.ts +++ b/packages/service/support/wallet/usage/schema.ts @@ -61,11 +61,11 @@ const UsageSchema = new Schema({ }); try { - UsageSchema.index({ teamId: 1, tmbId: 1, source: 1, time: -1 }, { background: true }); + UsageSchema.index({ teamId: 1, tmbId: 1, source: 1, time: -1 }); // timer task. clear dead team - // UsageSchema.index({ teamId: 1, time: -1 }, { background: true }); + // UsageSchema.index({ teamId: 1, time: -1 }); - UsageSchema.index({ time: 1 }, { background: true, expireAfterSeconds: 360 * 24 * 60 * 60 }); + UsageSchema.index({ time: 1 }, { expireAfterSeconds: 360 * 24 * 60 * 60 }); } catch (error) { console.log(error); } diff --git a/packages/service/worker/htmlStr2Md/utils.ts b/packages/service/worker/htmlStr2Md/utils.ts index 38112b92d34a..8384d005a132 100644 --- a/packages/service/worker/htmlStr2Md/utils.ts +++ b/packages/service/worker/htmlStr2Md/utils.ts @@ -1,9 +1,27 @@ import TurndownService from 'turndown'; import { ImageType } from '../readFile/type'; import { matchMdImgTextAndUpload } from '@fastgpt/global/common/string/markdown'; +import { getNanoid } from '@fastgpt/global/common/string/tools'; // @ts-ignore const turndownPluginGfm = require('joplin-turndown-plugin-gfm'); +const processBase64Images = (htmlContent: string) => { + const base64Regex = /src="data:([^;]+);base64,([^"]+)"/g; + const images: ImageType[] = []; + + const processedHtml = htmlContent.replace(base64Regex, (match, mime, base64Data) => { + const uuid = `IMAGE_${getNanoid(12)}_IMAGE`; + images.push({ + uuid, + base64: base64Data, + mime + }); + return `src="${uuid}"`; + }); + + return { processedHtml, images }; +}; + export const html2md = ( html: string ): { @@ -25,11 +43,14 @@ export const html2md = ( turndownService.remove(['i', 'script', 'iframe', 'style']); turndownService.use(turndownPluginGfm.gfm); - const { text, imageList } = matchMdImgTextAndUpload(html); + // Base64 img to id, otherwise it will occupy memory when going to md + const { processedHtml, images } = processBase64Images(html); + const md = turndownService.turndown(processedHtml); + const { text, imageList } = matchMdImgTextAndUpload(md); return { - rawText: turndownService.turndown(text), - imageList + rawText: text, + imageList: [...images, ...imageList] }; } catch (error) { console.log('html 2 markdown error', error); diff --git a/packages/service/worker/readFile/extension/rawText.ts b/packages/service/worker/readFile/extension/rawText.ts index 15e0bed83693..0f303f7458c5 100644 --- a/packages/service/worker/readFile/extension/rawText.ts +++ b/packages/service/worker/readFile/extension/rawText.ts @@ -24,7 +24,11 @@ export const readFileRawText = ({ buffer, encoding }: ReadRawTextByBuffer): Read return buffer.toString(encoding as BufferEncoding); } - return iconv.decode(buffer, encoding); + if (encoding) { + return iconv.decode(buffer, encoding); + } + + return buffer.toString('utf-8'); } catch (error) { return buffer.toString('utf-8'); } diff --git a/packages/service/worker/readFile/parseOffice.ts b/packages/service/worker/readFile/parseOffice.ts index 05dabf9ecc3e..177f5e2cdeeb 100644 --- a/packages/service/worker/readFile/parseOffice.ts +++ b/packages/service/worker/readFile/parseOffice.ts @@ -44,13 +44,15 @@ const parsePowerPoint = async ({ } // Returning an array of all the xml contents read using fs.readFileSync - const xmlContentArray = files.map((file) => { - try { - return fs.readFileSync(`${decompressPath}/${file.path}`, encoding); - } catch (err) { - return fs.readFileSync(`${decompressPath}/${file.path}`, 'utf-8'); - } - }); + const xmlContentArray = await Promise.all( + files.map((file) => { + try { + return fs.promises.readFile(`${decompressPath}/${file.path}`, encoding); + } catch (err) { + return fs.promises.readFile(`${decompressPath}/${file.path}`, 'utf-8'); + } + }) + ); let responseArr: string[] = []; diff --git a/packages/templates/register.ts b/packages/templates/register.ts index 4a18d4d96507..fae3334ea854 100644 --- a/packages/templates/register.ts +++ b/packages/templates/register.ts @@ -12,24 +12,24 @@ const getTemplateNameList = () => { return fs.readdirSync(templatesPath) as string[]; }; -const getFileTemplates = (): AppTemplateSchemaType[] => { +const getFileTemplates = async (): Promise => { const templateNames = getTemplateNameList(); - const appMarketTemplates = templateNames.map((name) => { - const fileContent = require(`./src/${name}/template.json`); + return Promise.all( + templateNames.map>(async (name) => { + const fileContent = (await import(`./src/${name}/template.json`))?.default; - return { - ...fileContent, - templateId: `${PluginSourceEnum.community}-${name}`, - isActive: true - }; - }); - - return appMarketTemplates; + return { + ...fileContent, + templateId: `${PluginSourceEnum.community}-${name}`, + isActive: true + }; + }) + ); }; const getAppTemplates = async () => { - const communityTemplates = getFileTemplates(); + const communityTemplates = await getFileTemplates(); const dbTemplates = await MongoAppTemplate.find(); diff --git a/packages/web/components/common/Icon/button.tsx b/packages/web/components/common/Icon/button.tsx index 38cd50a39174..9fde10ec46e0 100644 --- a/packages/web/components/common/Icon/button.tsx +++ b/packages/web/components/common/Icon/button.tsx @@ -18,7 +18,6 @@ const MyIconButton = ({ }: Props) => { return ( import('./icons/common/customTitleLight.svg'), 'common/data': () => import('./icons/common/data.svg'), 'common/dingtalkFill': () => import('./icons/common/dingtalkFill.svg'), + 'common/downArrowFill': () => import('./icons/common/downArrowFill.svg'), 'common/editor/resizer': () => import('./icons/common/editor/resizer.svg'), 'common/errorFill': () => import('./icons/common/errorFill.svg'), 'common/file/move': () => import('./icons/common/file/move.svg'), @@ -95,8 +96,10 @@ export const iconPaths = { 'common/variable': () => import('./icons/common/variable.svg'), 'common/viewLight': () => import('./icons/common/viewLight.svg'), 'common/voiceLight': () => import('./icons/common/voiceLight.svg'), + 'common/wallet': () => import('./icons/common/wallet.svg'), 'common/warn': () => import('./icons/common/warn.svg'), 'common/wechatFill': () => import('./icons/common/wechatFill.svg'), + 'common/wecom': () => import('./icons/common/wecom.svg'), configmap: () => import('./icons/configmap.svg'), copy: () => import('./icons/copy.svg'), 'core/app/aiFill': () => import('./icons/core/app/aiFill.svg'), @@ -346,6 +349,8 @@ export const iconPaths = { history: () => import('./icons/history.svg'), infoRounded: () => import('./icons/infoRounded.svg'), kbTest: () => import('./icons/kbTest.svg'), + key: () => import('./icons/key.svg'), + keyPrimary: () => import('./icons/keyPrimary.svg'), menu: () => import('./icons/menu.svg'), minus: () => import('./icons/minus.svg'), 'modal/AddClb': () => import('./icons/modal/AddClb.svg'), @@ -408,7 +413,6 @@ export const iconPaths = { 'support/bill/shoppingCart': () => import('./icons/support/bill/shoppingCart.svg'), 'support/bill/wallet': () => import('./icons/support/bill/wallet.svg'), 'support/outlink/apikeyFill': () => import('./icons/support/outlink/apikeyFill.svg'), - 'support/outlink/apikeyLight': () => import('./icons/support/outlink/apikeyLight.svg'), 'support/outlink/iframeLight': () => import('./icons/support/outlink/iframeLight.svg'), 'support/outlink/share': () => import('./icons/support/outlink/share.svg'), 'support/outlink/shareLight': () => import('./icons/support/outlink/shareLight.svg'), @@ -416,7 +420,6 @@ export const iconPaths = { 'support/permission/privateLight': () => import('./icons/support/permission/privateLight.svg'), 'support/permission/publicLight': () => import('./icons/support/permission/publicLight.svg'), 'support/team/group': () => import('./icons/support/team/group.svg'), - 'support/team/key': () => import('./icons/support/team/key.svg'), 'support/team/memberLight': () => import('./icons/support/team/memberLight.svg'), 'support/usage/usageRecordLight': () => import('./icons/support/usage/usageRecordLight.svg'), 'support/user/informLight': () => import('./icons/support/user/informLight.svg'), diff --git a/packages/web/components/common/Icon/icons/common/downArrowFill.svg b/packages/web/components/common/Icon/icons/common/downArrowFill.svg new file mode 100644 index 000000000000..d0f5035e4e49 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/downArrowFill.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/web/components/common/Icon/icons/common/wallet.svg b/packages/web/components/common/Icon/icons/common/wallet.svg new file mode 100644 index 000000000000..add3e92578c6 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/wallet.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/wecom.svg b/packages/web/components/common/Icon/icons/common/wecom.svg new file mode 100644 index 000000000000..1345e42ca097 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/wecom.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/web/components/common/Icon/icons/support/team/key.svg b/packages/web/components/common/Icon/icons/key.svg similarity index 100% rename from packages/web/components/common/Icon/icons/support/team/key.svg rename to packages/web/components/common/Icon/icons/key.svg diff --git a/packages/web/components/common/Icon/icons/keyPrimary.svg b/packages/web/components/common/Icon/icons/keyPrimary.svg new file mode 100644 index 000000000000..836c660793af --- /dev/null +++ b/packages/web/components/common/Icon/icons/keyPrimary.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/web/components/common/Icon/icons/support/outlink/apikeyLight.svg b/packages/web/components/common/Icon/icons/support/outlink/apikeyLight.svg deleted file mode 100644 index dee84de25baa..000000000000 --- a/packages/web/components/common/Icon/icons/support/outlink/apikeyLight.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - \ No newline at end of file diff --git a/packages/web/components/common/Tabs/FillRowTabs.tsx b/packages/web/components/common/Tabs/FillRowTabs.tsx index 7388af742820..f09834955f98 100644 --- a/packages/web/components/common/Tabs/FillRowTabs.tsx +++ b/packages/web/components/common/Tabs/FillRowTabs.tsx @@ -1,15 +1,15 @@ -import React from 'react'; +import React, { forwardRef } from 'react'; import { Flex, Box, BoxProps } from '@chakra-ui/react'; import MyIcon from '../Icon'; -type Props = Omit & { +type Props = Omit & { list: { icon?: string; label: string | React.ReactNode; - value: string; + value: T; }[]; - value: string; - onChange: (e: string) => void; + value: T; + onChange: (e: T) => void; }; const FillRowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props }: Props) => { @@ -61,4 +61,6 @@ const FillRowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props ); }; -export default FillRowTabs; +export default forwardRef(FillRowTabs) as ( + props: Props & { ref?: React.Ref } +) => JSX.Element; diff --git a/packages/web/i18n/en/account.json b/packages/web/i18n/en/account.json index b115fa506652..58155816a401 100644 --- a/packages/web/i18n/en/account.json +++ b/packages/web/i18n/en/account.json @@ -1,7 +1,14 @@ { + "active_model": "Available models", + "add_default_model": "Add a preset model", "api_key": "API key", "bills_and_invoices": "Bills", + "channel": "Channel", "confirm_logout": "Confirm to log out?", + "create_channel": "Add new channel", + "create_model": "Add new model", + "custom_model": "custom model", + "default_model": "Default model", "logout": "Sign out", "model_provider": "Model Provider", "notifications": "Notify", diff --git a/packages/web/i18n/en/account_info.json b/packages/web/i18n/en/account_info.json index dbda144151b4..442a6191b14d 100644 --- a/packages/web/i18n/en/account_info.json +++ b/packages/web/i18n/en/account_info.json @@ -10,9 +10,6 @@ "avatar_selection_exception": "Abnormal avatar selection", "balance": "balance", "billing_standard": "Standards", - "bind_notification_error": "Abnormal binding notification account", - "bind_notification_hint": "Please bind the notification receiving account to ensure that you can normally receive notifications such as package expiration reminders and ensure the normal operation of your services.", - "bind_notification_success": "Binding notification account successful", "cancel": "Cancel", "change": "change", "choose_avatar": "Click to select avatar", @@ -40,7 +37,6 @@ "month": "moon", "new_password": "New Password", "notification_receiving": "Notify", - "notification_receiving_hint": "Notification reception", "old_password": "Old Password", "openai_account_configuration": "OpenAI account configuration", "openai_account_setting_exception": "Setting OpenAI account exception", @@ -49,8 +45,8 @@ "package_expiry_time": "Expired", "package_usage_rules": "Package usage rules: The system will give priority to using more advanced packages, and the original unused packages will take effect later.", "password": "Password", - "password_length_error": "Password must be at least 4 characters and at most 60 characters", "password_mismatch": "Password Inconsistency: Two passwords are inconsistent", + "password_tip": "Password must be at least 6 characters long and contain at least two combinations: numbers, letters, or special characters", "password_update_error": "Exception when changing password", "password_update_success": "Password changed successfully", "pending_usage": "To be used", diff --git a/packages/web/i18n/en/account_team.json b/packages/web/i18n/en/account_team.json index 453b0a14f1ee..8641c3c8f921 100644 --- a/packages/web/i18n/en/account_team.json +++ b/packages/web/i18n/en/account_team.json @@ -1,10 +1,16 @@ { "action": "operate", "confirm_delete_group": "Confirm to delete group?", - "confirm_leave_team": "Confirmed to leave the team? \n \nAfter you log out, all your resources in the team (applications, knowledge bases, folders, managed groups, etc.) will be transferred to the team owner.", + "confirm_delete_member": "Confirm to delete member?", + "confirm_delete_org": "Confirm to delete organization?", + "confirm_leave_team": "Confirmed to leave the team? \nAfter exiting, all your resources in the team are transferred to the team owner.", "create_group": "Create group", + "create_org": "Create organization", + "create_sub_org": "Create sub-organization", "delete": "delete", + "delete_org": "Delete organization", "edit_info": "Edit information", + "edit_org_info": "Edit organization information", "group": "group", "group_name": "Group name", "label_sync": "Tag sync", @@ -12,8 +18,14 @@ "manage_member": "Managing members", "member": "member", "member_group": "Belonging to member group", + "move_member": "Move member", + "move_org": "Move organization", + "org": "organization", + "org_description": "Organization description", + "org_name": "Organization name", "owner": "owner", "permission": "Permissions", + "remark": "remark", "remove_tip": "Confirm to remove {{username}} from the team?", "retain_admin_permissions": "Keep administrator rights", "search_member_group_name": "Search member/group name", diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index 76e00a859941..08c7a74487d7 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -74,11 +74,19 @@ "code_error.team_error.ai_points_not_enough": "Insufficient AI Points", "code_error.team_error.app_amount_not_enough": "Application Limit Reached", "code_error.team_error.cannot_delete_default_group": "Cannot delete default group", + "code_error.team_error.cannot_delete_non_empty_org": "Cannot delete non-empty organization", + "code_error.team_error.cannot_modify_root_org": "Cannot modify root organization", + "code_error.team_error.cannot_move_to_sub_path": "Cannot move to same or subdirectory", "code_error.team_error.dataset_amount_not_enough": "Dataset Limit Reached", "code_error.team_error.dataset_size_not_enough": "Insufficient Dataset Capacity, Please Expand", "code_error.team_error.group_name_duplicate": "Duplicate group name", "code_error.team_error.group_name_empty": "Group name cannot be empty", "code_error.team_error.group_not_exist": "Group does not exist", + "code_error.team_error.not_user": "The member cannot be found", + "code_error.team_error.org_member_duplicated": "Duplicate organization member", + "code_error.team_error.org_member_not_exist": "Organization member does not exist", + "code_error.team_error.org_not_exist": "Organization does not exist", + "code_error.team_error.org_parent_not_exist": "Parent organization does not exist", "code_error.team_error.over_size": "error.team.overSize", "code_error.team_error.plugin_amount_not_enough": "Plugin Limit Reached", "code_error.team_error.re_rank_not_enough": "Unauthorized to Use Re-Rank", @@ -87,7 +95,7 @@ "code_error.team_error.website_sync_not_enough": "Unauthorized to Use Website Sync", "code_error.token_error_code.403": "Invalid Login Status, Please Re-login", "code_error.user_error.balance_not_enough": "Insufficient Account Balance", - "code_error.user_error.bin_visitor": "Identity Verification Failed", + "code_error.account_error": "Incorrect account name or password", "code_error.user_error.bin_visitor_guest": "You Are Currently a Guest, Unauthorized to Operate", "code_error.user_error.un_auth_user": "User Not Found", "common.Action": "Action", @@ -873,9 +881,11 @@ "error.code_error": "Verification code error", "error.fileNotFound": "File not found~", "error.inheritPermissionError": "Inherit permission Error", + "error.invalid_params": "Invalid parameter", "error.missingParams": "Insufficient parameters", "error.too_many_request": "Too many request", "error.upload_file_error_filename": "{{name}} Upload Failed", + "error.upload_image_error": "File upload failed", "error.username_empty": "Account cannot be empty", "extraction_results": "Extraction Results", "field_name": "Field Name", @@ -1028,7 +1038,15 @@ "support.user.Price": "Pricing", "support.user.User self info": "Profile", "support.user.auth.Sending Code": "Sending Code", + "support.user.auth.get_code": "Get Verification Code", + "support.user.auth.get_code_again": "s Get Again", "support.user.captcha_placeholder": "Please enter the verification code", + "support.user.info.bind_notification_error": "Abnormal binding notification account", + "support.user.info.bind_notification_hint": "Please bind the notification receiving account to ensure that you can receive notifications such as package expiration reminders, etc., to ensure the normal operation of your service.", + "support.user.info.bind_notification_success": "Binding notification account successful", + "support.user.info.code_required": "Verification code cannot be empty", + "support.user.info.notification_receiving_hint": "Notification reception", + "support.user.info.verification_code": "Verification Code", "support.user.inform.System message": "System Message", "support.user.login.Email": "Email", "support.user.login.Github": "GitHub Login", @@ -1041,7 +1059,7 @@ "support.user.login.Provider error": "Login Error, Please Try Again", "support.user.login.Username": "Username", "support.user.login.Wechat": "WeChat Login", - "support.user.login.can_not_login": "Cannot Log In, Click to Contact", + "support.user.login.can_not_login": "Cannot log in? Click here to contact us", "support.user.login.error": "Login Error", "support.user.login.security_failed": "Security Verification Failed", "support.user.login.wx_qr_login": "WeChat QR Code Login", @@ -1053,10 +1071,14 @@ "support.wallet.Ai point every thousand tokens_input": "Input:{{points}} points/1K tokens", "support.wallet.Ai point every thousand tokens_output": "Output:{{points}} points/1K tokens", "support.wallet.Amount": "Amount", + "support.wallet.App_amount_not_sufficient": "The number of your applications has reached the limit. Please upgrade your plan to continue using.", "support.wallet.Buy": "Buy", + "support.wallet.Dataset_amount_not_sufficient": "The number of your datasets has reached the limit. Please upgrade your plan to continue using.", + "support.wallet.Dataset_not_sufficient": "Your dataset capacity is insufficient. Please upgrade your plan or purchase additional dataset capacity to continue using.", "support.wallet.Not sufficient": "Insufficient AI Points, Please Upgrade Your Package or Purchase Additional AI Points to Continue Using.", "support.wallet.Plan expired time": "Package Expiration Time", "support.wallet.Standard Plan Detail": "Package Details", + "support.wallet.Team_member_over_size": "The number of your team members has reached the limit. Please upgrade your plan to continue using.", "support.wallet.To read plan": "View Package", "support.wallet.amount_0": "Purchase Quantity Cannot Be 0", "support.wallet.apply_invoice": "Apply for Invoice", @@ -1166,6 +1188,7 @@ "tag_list": "Tag List", "team_tag": "Team Tag", "textarea_variable_picker_tip": "Enter \"/\" to select a variable", + "unauth_token": "The certificate has expired, please log in again", "unit.character": "Character", "unit.minute": "Minute", "unit.seconds": "Second", diff --git a/packages/web/i18n/en/login.json b/packages/web/i18n/en/login.json index a1256d494fdc..8dfe1b826612 100644 --- a/packages/web/i18n/en/login.json +++ b/packages/web/i18n/en/login.json @@ -1,13 +1,14 @@ { "Chinese_ip_tip": "It is detected that you are a mainland Chinese IP, click to jump to visit the mainland China version.", "Login": "Login", - "forget_password": "Find password", + "forget_password": "Find Password", "login_failed": "Login failed", "login_success": "Login successful", "no_remind": "Don't remind again", "password_condition": "Password maximum 60 characters", - "policy_tip": "By useing, you agree to our", - "privacy": "Privacy policy", + "password_tip": "Password must be at least 6 characters long and contain at least two combinations: numbers, letters, or special characters", + "policy_tip": "By using this service, you agree to our", + "privacy": "Privacy Policy", "redirect": "Jump", "register": "Register", "root_password_placeholder": "The root user password is the value of the environment variable DEFAULT_ROOT_PSW", @@ -16,4 +17,4 @@ "agree": "agree", "cookies_tip": " This website uses cookies to provide a better service experience. By continuing to use the site, you agree to our Cookie Policy.", "privacy_policy": "Privacy Policy" -} \ No newline at end of file +} diff --git a/packages/web/i18n/en/user.json b/packages/web/i18n/en/user.json index 5f2ba68eef1c..091cffd3c668 100644 --- a/packages/web/i18n/en/user.json +++ b/packages/web/i18n/en/user.json @@ -28,7 +28,6 @@ "login.Dingtalk": "DingTalk Login", "manage_team": "Manage team", "name": "Name", - "notification.Bind Notification Pipe Hint": "Please bind a notification receiving account to ensure you receive notifications such as plan expiration reminders, ensuring your service runs smoothly.", "notification.remind_owner_bind": "Please remind the creator to bind a notification account", "operations": "Actions", "owner": "owner", @@ -38,9 +37,6 @@ "password.confirm": "Confirm Password", "password.email_phone_error": "Invalid Email/Phone Number Format", "password.email_phone_void": "Email/Phone Number Cannot Be Empty", - "password.get_code": "Get Verification Code", - "password.get_code_again": "Get Again in s", - "password.new_password": "New Password (4-20 characters)", "password.not_match": "Passwords Do Not Match", "password.password_condition": "Password must be between 4 and 20 characters", "password.password_required": "Password Cannot Be Empty", @@ -50,6 +46,7 @@ "password.to_login": "Go to Login", "password.verification_code": "Verification Code", "permission.Manage": "Admin", + "permission.Add": "Add Permissions", "permission.Manage tip": "Team admin with full permissions", "permission.Read": "Read Only", "permission.Read desc": "Members can only read related resources, cannot create new resources", @@ -61,6 +58,7 @@ "permission_des.manage": "Can create resources, invite, and delete members", "permission_des.read": "Members can only read related resources and cannot create new resources.", "permission_des.write": "In addition to readable resources, you can also create new resources", + "permission_add_tip": "After adding, you can check the permissions for them.", "permissions": "Permissions", "personal_information": "Me", "personalization": "Personalization", @@ -68,7 +66,7 @@ "register.confirm": "Confirm Registration", "register.register_account": "Register {{account}} Account", "register.success": "Registration Successful", - "register.to_login": "Already have an account? Login", + "register.to_login": "Already have an account? Go to Login", "search_user": "Search Username", "sso_auth_failed": "SSO authentication failed", "synchronization.button": "Sync Now", @@ -85,6 +83,7 @@ "team.Update Team": "Update team information", "team.add_collaborator": "Add Collaborator", "team.add_writer": "Add writable members", + "team.add_permission": "Add permissions", "team.avatar_and_name": "avatar", "team.belong_to_group": "Member group", "team.group.avatar": "Group avatar", @@ -106,10 +105,11 @@ "team.group.role.admin": "administrator", "team.group.role.member": "member", "team.group.role.owner": "owner", - "team.group.search_placeholder": "Search member/group name", + "search_group_org_user": "Search member/group/org name", "team.group.set_as_admin": "Set as administrator", "team.group.toast.can_not_delete_owner": "Owner cannot be deleted, please transfer first", "team.group.transfer_owner": "transfer owner", + "team.org.org": "Organization", "team.manage_collaborators": "Manage Collaborators", "team.no_collaborators": "No Collaborators", "team.write_role_member": "", diff --git a/packages/web/i18n/en/workflow.json b/packages/web/i18n/en/workflow.json index a76add2cc218..9ee85b9329f0 100644 --- a/packages/web/i18n/en/workflow.json +++ b/packages/web/i18n/en/workflow.json @@ -49,7 +49,7 @@ "execution_error": "Execution Error", "extraction_requirements_description": "Extraction Requirements Description", "extraction_requirements_description_detail": "Provide AI with some background knowledge or requirements to guide it in completing the task better.\\nThis input box can use global variables.", - "extraction_requirements_placeholder": "For example: \\n1. The current time is: {{cTime}}. You are a lab reservation assistant, and your task is to help users reserve a lab by extracting the corresponding reservation information from the text.\\n2. You are a Google search assistant, and you need to extract suitable search terms from the text.", + "extraction_requirements_placeholder": "For example: 1. The current time is: {{cTime}}. \nYou are a laboratory reservation assistant. Your task is to help users make laboratory reservations and obtain the corresponding reservation information from the text.\n\n2. You are the Google Search Assistant and need to extract appropriate search terms from text.", "feedback_text": "Feedback Text", "field_description": "Field Description", "field_description_placeholder": "Describe the function of this input field. If it is a tool call parameter, this description will affect the quality of the model generation.", diff --git a/packages/web/i18n/zh-CN/account.json b/packages/web/i18n/zh-CN/account.json index 85b0e4954865..5eae52e1fb45 100644 --- a/packages/web/i18n/zh-CN/account.json +++ b/packages/web/i18n/zh-CN/account.json @@ -1,7 +1,14 @@ { + "active_model": "可用模型", + "add_default_model": "添加预设模型", "api_key": "API 密钥", "bills_and_invoices": "账单与发票", + "channel": "渠道", "confirm_logout": "确认退出登录?", + "create_channel": "新增渠道", + "create_model": "新增模型", + "custom_model": "自定义模型", + "default_model": "预设模型", "logout": "登出", "model_provider": "模型提供商", "notifications": "通知", diff --git a/packages/web/i18n/zh-CN/account_info.json b/packages/web/i18n/zh-CN/account_info.json index 6059523dd237..efd52c90a39c 100644 --- a/packages/web/i18n/zh-CN/account_info.json +++ b/packages/web/i18n/zh-CN/account_info.json @@ -10,9 +10,6 @@ "avatar_selection_exception": "头像选择异常", "balance": "余额", "billing_standard": "计费标准", - "bind_notification_error": "绑定通知账号异常", - "bind_notification_hint": "请绑定通知接收账号,以确保您能正常接收套餐过期提醒等通知,保障您的服务正常运行。", - "bind_notification_success": "绑定通知账号成功", "cancel": "取消", "change": "变更", "choose_avatar": "点击选择头像", @@ -40,15 +37,14 @@ "month": "月", "new_password": "新密码", "notification_receiving": "通知接收", - "notification_receiving_hint": "通知接收", "old_password": "旧密码", "package_and_usage": "套餐与用量", "package_details": "套餐详情", "package_expiry_time": "套餐到期时间", "package_usage_rules": "套餐使用规则:系统优先使用更高级的套餐,原未用完的套餐将延后生效", "password": "密码", - "password_length_error": "密码最少 4 位最多 60 位", "password_mismatch": "密码不一致: 两次密码不一致", + "password_tip": "密码至少 6 位,且至少包含两种组合:数字、字母或特殊字符", "password_update_error": "修改密码异常", "password_update_success": "修改密码成功", "pending_usage": "待使用", diff --git a/packages/web/i18n/zh-CN/account_team.json b/packages/web/i18n/zh-CN/account_team.json index 720dff39f1ab..e562ef7c9989 100644 --- a/packages/web/i18n/zh-CN/account_team.json +++ b/packages/web/i18n/zh-CN/account_team.json @@ -1,29 +1,39 @@ -{ - "total_team_members": "共 {{amount}} 名成员", - "member": "成员", - "group": "群组", - "permission": "权限", - "user_name": "用户名", - "member_group": "所属成员组", - "action": "操作", - "waiting": "待接受", - "remove_tip": "确认将 {{username}} 移出团队?", - - "confirm_leave_team": "确认离开该团队? \n 退出后,您在该团队所有的资源( 应用、知识库、文件夹、管理的群组等)均转让给团队所有者。", - "leave_team_failed": "离开团队异常", - "label_sync": "标签同步", - "user_team_invite_member": "邀请成员", - "user_team_leave_team": "离开团队", - "user_team_leave_team_failed": "离开团队失败", - "create_group": "创建群组", - "search_member_group_name": "搜索成员/群组名称", - "confirm_delete_group": "确认删除群组?", - "group_name": "群组名称", - "owner": "所有者", - "manage_member": "管理成员", - "edit_info": "编辑信息", - - "transfer_ownership": "转让所有者", - "delete": "删除", - "retain_admin_permissions": "保留管理员权限" -} +{ + "action": "操作", + "confirm_delete_group": "确认删除群组?", + "confirm_delete_member": "确认删除成员?", + "confirm_delete_org": "确认删除该部门?", + "confirm_leave_team": "确认离开该团队? \n退出后,您在该团队所有的资源均转让给团队所有者。", + "create_group": "创建群组", + "create_org": "创建部门", + "create_sub_org": "创建子部门", + "delete": "删除", + "delete_org": "删除部门", + "edit_info": "编辑信息", + "edit_org_info": "编辑部门信息", + "group": "群组", + "group_name": "群组名称", + "label_sync": "标签同步", + "leave_team_failed": "离开团队异常", + "manage_member": "管理成员", + "member": "成员", + "member_group": "所属群组", + "move_member": "移动成员", + "move_org": "移动部门", + "org": "部门", + "org_description": "介绍", + "org_name": "部门名称", + "owner": "所有者", + "permission": "权限", + "remark": "备注", + "remove_tip": "确认将 {{username}} 移出团队?", + "retain_admin_permissions": "保留管理员权限", + "search_member_group_name": "搜索成员/群组名称", + "total_team_members": "共 {{amount}} 名成员", + "transfer_ownership": "转让所有者", + "user_name": "用户名", + "user_team_invite_member": "邀请成员", + "user_team_leave_team": "离开团队", + "user_team_leave_team_failed": "离开团队失败", + "waiting": "待接受" +} diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index 5720977c162c..f4c97061ff0d 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -78,11 +78,19 @@ "code_error.team_error.ai_points_not_enough": "", "code_error.team_error.app_amount_not_enough": "应用数量已达上限~", "code_error.team_error.cannot_delete_default_group": "不能删除默认群组", + "code_error.team_error.cannot_delete_non_empty_org": "不能删除非空部门", + "code_error.team_error.cannot_modify_root_org": "不能修改根部门", + "code_error.team_error.cannot_move_to_sub_path": "不能移动到相同或子目录", "code_error.team_error.dataset_amount_not_enough": "知识库数量已达上限~", "code_error.team_error.dataset_size_not_enough": "知识库容量不足,请先扩容~", "code_error.team_error.group_name_duplicate": "群组名称重复", "code_error.team_error.group_name_empty": "群组名称不能为空", "code_error.team_error.group_not_exist": "群组不存在", + "code_error.team_error.not_user": "找不到该成员", + "code_error.team_error.org_member_duplicated": "重复的部门成员", + "code_error.team_error.org_member_not_exist": "部门成员不存在", + "code_error.team_error.org_not_exist": "部门不存在", + "code_error.team_error.org_parent_not_exist": "父部门不存在", "code_error.team_error.over_size": "error.team.overSize", "code_error.team_error.plugin_amount_not_enough": "插件数量已达上限~", "code_error.team_error.re_rank_not_enough": "无权使用检索重排~", @@ -91,7 +99,7 @@ "code_error.team_error.website_sync_not_enough": "无权使用Web站点同步~", "code_error.token_error_code.403": "登录状态无效,请重新登录", "code_error.user_error.balance_not_enough": "账号余额不足~", - "code_error.user_error.bin_visitor": "您的身份校验未通过", + "code_error.account_error": "账号名或密码错误", "code_error.user_error.bin_visitor_guest": "您当前身份为游客,无权操作", "code_error.user_error.un_auth_user": "找不到该用户", "common.Action": "操作", @@ -876,9 +884,11 @@ "error.code_error": "验证码错误", "error.fileNotFound": "文件找不到了~", "error.inheritPermissionError": "权限继承错误", + "error.invalid_params": "参数无效", "error.missingParams": "参数缺失", "error.too_many_request": "请求太频繁了,请稍后重试", "error.upload_file_error_filename": "{{name}} 上传失败", + "error.upload_image_error": "上传文件失败", "error.username_empty": "账号不能为空", "extraction_results": "提取结果", "field_name": "字段名", @@ -1031,7 +1041,15 @@ "support.user.Price": "计费标准", "support.user.User self info": "个人信息", "support.user.auth.Sending Code": "正在发送", + "support.user.auth.get_code": "获取验证码", + "support.user.auth.get_code_again": "s后重新获取", "support.user.captcha_placeholder": "请输入验证码", + "support.user.info.bind_notification_error": "绑定通知账号异常", + "support.user.info.bind_notification_hint": "请绑定通知接收账号,以确保您能正常接收套餐过期提醒等通知,保障您的服务正常运行。", + "support.user.info.bind_notification_success": "绑定通知账号成功", + "support.user.info.code_required": "验证码不能为空", + "support.user.info.notification_receiving_hint": "通知接收", + "support.user.info.verification_code": "验证码", "support.user.inform.System message": "系统消息", "support.user.login.Email": "邮箱", "support.user.login.Github": "GitHub 登录", @@ -1056,10 +1074,14 @@ "support.wallet.Ai point every thousand tokens_input": "输入:{{points}} 积分/1K tokens", "support.wallet.Ai point every thousand tokens_output": "输出:{{points}} 积分/1K tokens", "support.wallet.Amount": "金额", + "support.wallet.App_amount_not_sufficient": "您的应用数量已达上限,请升级套餐后继续使用。", "support.wallet.Buy": "购买", + "support.wallet.Dataset_amount_not_sufficient": "您的知识库数量已达上限,请升级套餐后继续使用。", + "support.wallet.Dataset_not_sufficient": "您的知识库容量不足,请先升级套餐或购买额外知识库容量后继续使用。", "support.wallet.Not sufficient": "您的 AI 积分不足,请先升级套餐或购买额外 AI 积分后继续使用。", "support.wallet.Plan expired time": "套餐到期时间", "support.wallet.Standard Plan Detail": "套餐详情", + "support.wallet.Team_member_over_size": "您的团队成员数量已达上限,请升级套餐后继续使用。", "support.wallet.To read plan": "查看套餐", "support.wallet.amount_0": "购买数量不能为0", "support.wallet.apply_invoice": "申请开票", @@ -1169,6 +1191,7 @@ "tag_list": "标签列表", "team_tag": "团队标签", "textarea_variable_picker_tip": "输入\"/\"可选择变量", + "unauth_token": "凭证已过期,请重新登录", "unit.character": "字符", "unit.minute": "分钟", "unit.seconds": "秒", diff --git a/packages/web/i18n/zh-CN/login.json b/packages/web/i18n/zh-CN/login.json index 92eb9606b7b4..f3cc74a89ead 100644 --- a/packages/web/i18n/zh-CN/login.json +++ b/packages/web/i18n/zh-CN/login.json @@ -1,19 +1,21 @@ { + "Chinese_ip_tip": "检测到您是中国大陆 IP,点击跳转访问中国大陆版。", "Login": "登录", + "agree": "同意", + "cookies_tip": "本网站使用 cookies 提供更好的服务体验。继续使用即表示您同意我们的 Cookie 政策。", "forget_password": "忘记密码?", "login_failed": "登录异常", "login_success": "登录成功", + "no_remind": "不再提醒", "password_condition": "密码最多 60 位", + "password_tip": "密码至少 6 位,且至少包含两种组合:数字、字母或特殊字符", "policy_tip": "使用即代表你同意我们的", "privacy": "隐私协议", + "privacy_policy": "隐私政策", + "redirect": "跳转", "register": "注册账号", "root_password_placeholder": "root 用户密码为环境变量 DEFAULT_ROOT_PSW 的值", "terms": "服务协议", "use_root_login": "使用 root 用户登录", - "redirect": "跳转", - "no_remind": "不再提醒", - "Chinese_ip_tip": "检测到您是中国大陆 IP,点击跳转访问中国大陆版。", - "agree": "同意", - "cookies_tip": "本网站使用 cookies 提供更好的服务体验。继续使用即表示您同意我们的 Cookie 政策。", - "privacy_policy": "隐私政策" -} \ No newline at end of file + "wecom": "企业微信" +} diff --git a/packages/web/i18n/zh-CN/user.json b/packages/web/i18n/zh-CN/user.json index 0c3e5012657b..e0ddec7904a1 100644 --- a/packages/web/i18n/zh-CN/user.json +++ b/packages/web/i18n/zh-CN/user.json @@ -28,7 +28,6 @@ "login.Dingtalk": "钉钉登录", "manage_team": "管理团队", "name": "名称", - "notification.Bind Notification Pipe Hint": "请绑定通知接收账号,以确保您能正常接收套餐过期提醒等通知,保障您的服务正常运行。", "notification.remind_owner_bind": "请提醒创建者绑定通知账号", "operations": "操作", "owner": "所有者", @@ -38,9 +37,6 @@ "password.confirm": "确认密码", "password.email_phone_error": "邮箱/手机号格式错误", "password.email_phone_void": "邮箱/手机号不能为空", - "password.get_code": "获取验证码", - "password.get_code_again": "s后重新获取", - "password.new_password": "新密码(4~20位)", "password.not_match": "两次密码不一致", "password.password_condition": "密码最少 4 位最多 20 位", "password.password_required": "密码不能为空", @@ -53,6 +49,7 @@ "permission.Manage tip": "团队管理员,拥有全部权限", "permission.Read": "仅读", "permission.Read desc": "成员仅可阅读相关资源,无法新建资源", + "permission.Add": "添加权限", "permission.Write": "可写", "permission.Write tip": "除了可读资源外,还可以新建新的资源", "permission.only_collaborators": "仅协作者访问", @@ -61,6 +58,7 @@ "permission_des.manage": "可创建资源、邀请、删除成员", "permission_des.read": "成员仅可阅读相关资源,无法新建资源", "permission_des.write": "除了可读资源外,还可以新建新的资源", + "permission_add_tip": "添加后,您可为其勾选权限。", "permissions": "权限", "personal_information": "个人信息", "personalization": "个性化", @@ -85,8 +83,9 @@ "team.Update Team": "更新团队信息", "team.add_collaborator": "添加协作者", "team.add_writer": "添加可写成员", + "team.add_permission": "添加权限", "team.avatar_and_name": "头像 & 名称", - "team.belong_to_group": "所属成员组", + "team.belong_to_group": "所属群组", "team.group.avatar": "群头像", "team.group.create": "创建群组", "team.group.create_failed": "创建群组失败", @@ -106,10 +105,11 @@ "team.group.role.admin": "管理员", "team.group.role.member": "成员", "team.group.role.owner": "所有者", - "team.group.search_placeholder": "搜索成员/群组名称", + "search_group_org_user": "搜索成员/部门/群组名称", "team.group.set_as_admin": "设为管理员", "team.group.toast.can_not_delete_owner": "不能删除所有者, 请先转让", "team.group.transfer_owner": "转让所有者", + "team.org.org": "部门", "team.manage_collaborators": "管理协作者", "team.no_collaborators": "暂无协作者", "team.write_role_member": "可写权限", diff --git a/packages/web/i18n/zh-CN/workflow.json b/packages/web/i18n/zh-CN/workflow.json index 4b7b14ac405a..47696b48dc83 100644 --- a/packages/web/i18n/zh-CN/workflow.json +++ b/packages/web/i18n/zh-CN/workflow.json @@ -49,7 +49,7 @@ "execution_error": "运行错误", "extraction_requirements_description": "提取要求描述", "extraction_requirements_description_detail": "给AI一些对应的背景知识或要求描述,引导AI更好的完成任务。\\n该输入框可使用全局变量。", - "extraction_requirements_placeholder": "例如: \\n1. 当前时间为: {{cTime}}。你是一个实验室预约助手,你的任务是帮助用户预约实验室,从文本中获取对应的预约信息。\\n2. 你是谷歌搜索助手,需要从文本中提取出合适的搜索词。", + "extraction_requirements_placeholder": "例如: 1. 当前时间为: {{cTime}}。你是一个实验室预约助手,你的任务是帮助用户预约实验室,从文本中获取对应的预约信息。\n2. 你是谷歌搜索助手,需要从文本中提取出合适的搜索词。", "feedback_text": "反馈的文本", "field_description": "字段描述", "field_description_placeholder": "描述该输入字段的功能,如果为工具调用参数,则该描述会影响模型生成的质量", diff --git a/packages/web/i18n/zh-Hant/account.json b/packages/web/i18n/zh-Hant/account.json index 395ebe1d2ede..59130d9daf35 100644 --- a/packages/web/i18n/zh-Hant/account.json +++ b/packages/web/i18n/zh-Hant/account.json @@ -1,7 +1,14 @@ { + "active_model": "可用模型", + "add_default_model": "新增預設模型", "api_key": "API 金鑰", "bills_and_invoices": "帳單與發票", + "channel": "頻道", "confirm_logout": "確認登出登入?", + "create_channel": "新增頻道", + "create_model": "新增模型", + "custom_model": "自訂模型", + "default_model": "預設模型", "logout": "登出", "model_provider": "模型提供者", "notifications": "通知", diff --git a/packages/web/i18n/zh-Hant/account_info.json b/packages/web/i18n/zh-Hant/account_info.json index a2792aaad12d..e7e34366d00e 100644 --- a/packages/web/i18n/zh-Hant/account_info.json +++ b/packages/web/i18n/zh-Hant/account_info.json @@ -10,9 +10,6 @@ "avatar_selection_exception": "頭像選擇異常", "balance": "餘額", "billing_standard": "計費標準", - "bind_notification_error": "綁定通知帳號異常", - "bind_notification_hint": "請綁定通知接收帳號,確保您能正常接收套餐過期提醒等通知,保障您的服務正常運作。", - "bind_notification_success": "綁定通知帳號成功", "cancel": "取消", "change": "變更", "choose_avatar": "點選選擇頭像", @@ -40,7 +37,6 @@ "month": "月", "new_password": "新密碼", "notification_receiving": "通知接收", - "notification_receiving_hint": "通知接收", "old_password": "舊密碼", "openai_account_configuration": "OpenAI 帳號配置", "openai_account_setting_exception": "設定 OpenAI 帳號異常", @@ -49,8 +45,8 @@ "package_expiry_time": "套餐到期時間", "package_usage_rules": "套餐使用規則:系統優先使用更進階的套餐,原未用完的套餐將延遲生效", "password": "密碼", - "password_length_error": "密碼最少 4 位最多 60 位", "password_mismatch": "密碼不一致: 兩次密碼不一致", + "password_tip": "密碼至少 6 位,且至少包含兩種組合:數字、字母或特殊字符", "password_update_error": "修改密碼異常", "password_update_success": "修改密碼成功", "pending_usage": "待使用", diff --git a/packages/web/i18n/zh-Hant/account_team.json b/packages/web/i18n/zh-Hant/account_team.json index c02d361e162e..94a21818b3f5 100644 --- a/packages/web/i18n/zh-Hant/account_team.json +++ b/packages/web/i18n/zh-Hant/account_team.json @@ -1,10 +1,16 @@ { "action": "操作", "confirm_delete_group": "確認刪除群組?", - "confirm_leave_team": "確認離開該團隊? \n \n退出後,您在該團隊所有的資源( 應用程式、知識庫、資料夾、管理的群組等)均轉讓給團隊所有者。", + "confirm_delete_member": "確認刪除成員?", + "confirm_delete_org": "確認刪除該部門?", + "confirm_leave_team": "確認離開該團隊? \n退出後,您在該團隊所有的資源轉讓給團隊所有者。", "create_group": "建立群組", + "create_org": "創建部門", + "create_sub_org": "創建子部門", "delete": "刪除", + "delete_org": "刪除部門", "edit_info": "編輯訊息", + "edit_org_info": "編輯部門資訊", "group": "群組", "group_name": "群組名稱", "label_sync": "標籤同步", @@ -12,8 +18,14 @@ "manage_member": "管理成員", "member": "成員", "member_group": "所屬成員組", + "move_member": "移動成員", + "move_org": "行動部門", + "org": "組織", + "org_description": "介紹", + "org_name": "部門名稱", "owner": "擁有者", "permission": "權限", + "remark": "備註", "remove_tip": "確認將 {{username}} 移出團隊?", "retain_admin_permissions": "保留管理員權限", "search_member_group_name": "搜尋成員/群組名稱", diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index f046d02c5ce4..1604a0ffd36a 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -74,11 +74,19 @@ "code_error.team_error.ai_points_not_enough": "AI 點數不足", "code_error.team_error.app_amount_not_enough": "已達應用程式數量上限", "code_error.team_error.cannot_delete_default_group": "無法刪除預設群組", + "code_error.team_error.cannot_delete_non_empty_org": "無法刪除非空組織", + "code_error.team_error.cannot_modify_root_org": "無法修改根組織", + "code_error.team_error.cannot_move_to_sub_path": "無法移動到相同或子目錄", "code_error.team_error.dataset_amount_not_enough": "已達知識庫數量上限", "code_error.team_error.dataset_size_not_enough": "知識庫容量不足,請先擴充容量", "code_error.team_error.group_name_duplicate": "群組名稱重複", "code_error.team_error.group_name_empty": "群組名稱不能為空", "code_error.team_error.group_not_exist": "群組不存在", + "code_error.team_error.not_user": "找不到該成員", + "code_error.team_error.org_member_duplicated": "重複的組織成員", + "code_error.team_error.org_member_not_exist": "組織成員不存在", + "code_error.team_error.org_not_exist": "組織不存在", + "code_error.team_error.org_parent_not_exist": "父組織不存在", "code_error.team_error.over_size": "error.team.overSize", "code_error.team_error.plugin_amount_not_enough": "已達外掛程式數量上限", "code_error.team_error.re_rank_not_enough": "無權使用結果重新排名", @@ -87,7 +95,7 @@ "code_error.team_error.website_sync_not_enough": "無權使用網站同步", "code_error.token_error_code.403": "登入狀態無效,請重新登入", "code_error.user_error.balance_not_enough": "帳戶餘額不足", - "code_error.user_error.bin_visitor": "身份驗證未通過", + "code_error.account_error": "帳號名稱或密碼錯誤", "code_error.user_error.bin_visitor_guest": "您目前身份為訪客,無權操作", "code_error.user_error.un_auth_user": "找不到此使用者", "common.Action": "操作", @@ -874,9 +882,11 @@ "error.code_error": "驗證碼錯誤", "error.fileNotFound": "找不到檔案", "error.inheritPermissionError": "繼承權限錯誤", + "error.invalid_params": "參數無效", "error.missingParams": "參數不足", "error.too_many_request": "請求太頻繁了,請稍後重試", "error.upload_file_error_filename": "{{name}} 上傳失敗", + "error.upload_image_error": "上傳文件失敗", "error.username_empty": "帳號不能為空", "extraction_results": "提取結果", "field_name": "欄位名稱", @@ -1028,7 +1038,15 @@ "support.user.Price": "計費標準", "support.user.User self info": "個人資訊", "support.user.auth.Sending Code": "正在傳送驗證碼", + "support.user.auth.get_code": "取得驗證碼", + "support.user.auth.get_code_again": "秒後重新取得", "support.user.captcha_placeholder": "請輸入驗證碼", + "support.user.info.bind_notification_error": "綁定通知帳號異常", + "support.user.info.bind_notification_hint": "請綁定通知接收帳號,確保您能正常接收套餐過期提醒等通知,保障您的服務正常運作。", + "support.user.info.bind_notification_success": "綁定通知帳號成功", + "support.user.info.code_required": "驗證碼不能為空", + "support.user.info.notification_receiving_hint": "通知接收", + "support.user.info.verification_code": "驗證碼", "support.user.inform.System message": "系統訊息", "support.user.login.Email": "電子郵件", "support.user.login.Github": "GitHub 登入", @@ -1053,10 +1071,14 @@ "support.wallet.Ai point every thousand tokens_input": "輸入:{{points}} 积分/1K tokens", "support.wallet.Ai point every thousand tokens_output": "輸出:{{points}} 积分/1K tokens", "support.wallet.Amount": "金額", + "support.wallet.App_amount_not_sufficient": "您的應用數量已達上限,請升級套餐後繼續使用。", "support.wallet.Buy": "購買", + "support.wallet.Dataset_amount_not_sufficient": "您的知識庫數量已達上限,請升級套餐後繼續使用。", + "support.wallet.Dataset_not_sufficient": "您的知識庫容量不足,請先升級套餐或購買額外知識庫容量後繼續使用。", "support.wallet.Not sufficient": "您的 AI 點數不足,請先升級方案或購買額外 AI 點數後繼續使用。", "support.wallet.Plan expired time": "方案到期時間", "support.wallet.Standard Plan Detail": "方案詳細資訊", + "support.wallet.Team_member_over_size": "您的團隊成員數量已達上限,請升級套餐後繼續使用。", "support.wallet.To read plan": "檢視方案", "support.wallet.amount_0": "購買數量不能為 0", "support.wallet.apply_invoice": "申請發票", @@ -1166,6 +1188,7 @@ "tag_list": "標籤列表", "team_tag": "團隊標籤", "textarea_variable_picker_tip": "輸入「/」以選擇變數", + "unauth_token": "憑證已過期,請重新登入", "unit.character": "字元", "unit.minute": "分鐘", "unit.seconds": "秒", diff --git a/packages/web/i18n/zh-Hant/login.json b/packages/web/i18n/zh-Hant/login.json index db26897436ab..51bafc851520 100644 --- a/packages/web/i18n/zh-Hant/login.json +++ b/packages/web/i18n/zh-Hant/login.json @@ -1,19 +1,20 @@ { "Chinese_ip_tip": "偵測到您使用中國大陸 IP,點選這裡前往中國大陸版本。", "Login": "登入", + "agree": "同意", + "cookies_tip": "本網站使用 cookies 提供更好的服務體驗。繼續使用即表示您同意我們的 Cookie 政策。", "forget_password": "忘記密碼?", "login_failed": "登入失敗", "login_success": "登入成功", "no_remind": "不再提醒", "password_condition": "密碼最多 60 個字元", + "password_tip": "密碼至少 6 位,且至少包含兩種組合:數字、字母或特殊字符", "policy_tip": "使用即代表您同意我們的", "privacy": "隱私權政策", + "privacy_policy": "隱私權政策", "redirect": "跳轉", "register": "註冊帳號", "root_password_placeholder": "root 使用者密碼為環境變數 DEFAULT_ROOT_PSW 的值", "terms": "服務條款", - "use_root_login": "使用 root 使用者登入", - "agree": "同意", - "cookies_tip": "本網站使用 cookies 提供更好的服務體驗。繼續使用即表示您同意我們的 Cookie 政策。", - "privacy_policy": "隱私權政策" -} \ No newline at end of file + "use_root_login": "使用 root 使用者登入" +} diff --git a/packages/web/i18n/zh-Hant/user.json b/packages/web/i18n/zh-Hant/user.json index f743d765ab05..049178369b25 100644 --- a/packages/web/i18n/zh-Hant/user.json +++ b/packages/web/i18n/zh-Hant/user.json @@ -28,7 +28,6 @@ "login.Dingtalk": "釘釘登入", "manage_team": "管理團隊", "name": "名稱", - "notification.Bind Notification Pipe Hint": "請綁定通知接收帳號,以確保您能正常接收方案到期提醒等通知,保障您的服務正常運作。", "notification.remind_owner_bind": "請提醒建立者綁定通知帳號", "operations": "操作", "owner": "擁有者", @@ -38,9 +37,6 @@ "password.confirm": "確認密碼", "password.email_phone_error": "電子郵件/手機號碼格式錯誤", "password.email_phone_void": "電子郵件/手機號碼不能空白", - "password.get_code": "取得驗證碼", - "password.get_code_again": "秒後重新取得", - "password.new_password": "新密碼(4 至 20 字元)", "password.not_match": "兩次輸入的密碼不相符", "password.password_condition": "密碼長度需介於 4 至 20 字元之間", "password.password_required": "密碼不能空白", @@ -53,6 +49,7 @@ "permission.Manage tip": "團隊管理員,擁有完整權限", "permission.Read": "唯讀", "permission.Read desc": "成員僅能閱讀相關資源,無法建立新資源", + "permission.Add": "新增權限", "permission.Write": "可寫入", "permission.Write tip": "除了可讀取資源外,還可以建立新的資源", "permission.only_collaborators": "僅協作者可存取", @@ -61,6 +58,7 @@ "permission_des.manage": "可建立資源、邀請及刪除成員", "permission_des.read": "成員僅能閱讀相關資源,無法建立新資源", "permission_des.write": "除了可讀取資源外,還可以建立新的資源", + "permission_add_tip": "添加後,您可為其勾選權限。", "permissions": "權限", "personal_information": "個人資料", "personalization": "個人化", @@ -85,6 +83,7 @@ "team.Update Team": "更新團隊資訊", "team.add_collaborator": "新增協作者", "team.add_writer": "新增可寫入成員", + "team.add_permission": "新增權限", "team.avatar_and_name": "頭像與名稱", "team.belong_to_group": "所屬成員群組", "team.group.avatar": "群組頭像", @@ -106,10 +105,11 @@ "team.group.role.admin": "管理員", "team.group.role.member": "成員", "team.group.role.owner": "擁有者", - "team.group.search_placeholder": "搜尋成員/群組名稱", + "search_group_org_user": "搜尋成員/部門/群組名稱", "team.group.set_as_admin": "設為管理員", "team.group.toast.can_not_delete_owner": "無法刪除擁有者,請先轉移擁有權", "team.group.transfer_owner": "轉移擁有者", + "team.org.org": "組織", "team.manage_collaborators": "管理協作者", "team.no_collaborators": "目前沒有協作者", "team.write_role_member": "可寫入權限", diff --git a/packages/web/i18n/zh-Hant/workflow.json b/packages/web/i18n/zh-Hant/workflow.json index d8ec45aedc96..765d16439fe8 100644 --- a/packages/web/i18n/zh-Hant/workflow.json +++ b/packages/web/i18n/zh-Hant/workflow.json @@ -49,7 +49,7 @@ "execution_error": "執行錯誤", "extraction_requirements_description": "擷取需求描述", "extraction_requirements_description_detail": "提供 AI 相對應的背景知識或需求描述,引導 AI 更好地完成任務。\\n這個輸入框可以使用全域變數。", - "extraction_requirements_placeholder": "例如:\\n1. 目前時間為:{{cTime}}。您是一位實驗室預約助理,您的任務是協助使用者預約實驗室,從文字中取得對應的預約資訊。\\n2. 您是 Google 搜尋助理,需要從文字中擷取出合適的搜尋詞。", + "extraction_requirements_placeholder": "例如: 1. 目前時間為: {{cTime}}。\n你是實驗室預約助手,你的任務是幫助使用者預約實驗室,從文字中取得對應的預約資訊。\n\n2. 你是Google搜尋助手,需要從文字中提取出合適的搜尋字詞。", "feedback_text": "回饋文字", "field_description": "欄位描述", "field_description_placeholder": "描述這個輸入欄位的功能,如果是工具呼叫參數,這個描述會影響模型產生的品質", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b94bfee3b65b..e9216fe5d8ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -139,6 +139,9 @@ importers: '@node-rs/jieba': specifier: 1.10.0 version: 1.10.0 + '@wecom/jssdk': + specifier: ^2.2.5 + version: 2.2.5 '@xmldom/xmldom': specifier: ^0.8.10 version: 0.8.10 @@ -581,7 +584,7 @@ importers: version: 1.77.8 ts-jest: specifier: ^29.1.0 - version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3) + version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0))(typescript@5.5.3) use-context-selector: specifier: ^1.4.4 version: 1.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.23.2) @@ -721,7 +724,7 @@ importers: version: 6.3.4 ts-jest: specifier: ^29.1.0 - version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3) + version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0))(typescript@5.5.3) ts-loader: specifier: ^9.4.3 version: 9.5.1(typescript@5.5.3)(webpack@5.92.1) @@ -2022,7 +2025,7 @@ packages: '@emotion/use-insertion-effect-with-fallbacks@1.0.1': resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==} peerDependencies: - react: '>=16.8.0' + react: 18.3.1 '@emotion/utils@1.2.1': resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==} @@ -2645,8 +2648,8 @@ packages: resolution: {integrity: sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==} peerDependencies: monaco-editor: '>= 0.25.0 < 1' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: 18.3.1 + react-dom: 18.3.1 '@mongodb-js/saslprep@1.1.9': resolution: {integrity: sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==} @@ -2997,8 +3000,8 @@ packages: '@reactflow/node-resizer@2.2.14': resolution: {integrity: sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==} peerDependencies: - react: '>=17' - react-dom: '>=17' + react: 18.3.1 + react-dom: 18.3.1 '@reactflow/node-toolbar@1.3.14': resolution: {integrity: sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==} @@ -3715,6 +3718,9 @@ packages: '@webassemblyjs/wast-printer@1.12.1': resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==} + '@wecom/jssdk@2.2.5': + resolution: {integrity: sha512-qOBAsfqaiYM8jZHWYs/atHSpJhsLdZVNaxHQdmEQ7ZWul/GZMt4P5VY8Nf7GII7GhG8z/k+r37Dto6qtAaRqow==} + '@xmldom/xmldom@0.8.10': resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} engines: {node: '>=10.0.0'} @@ -7137,8 +7143,8 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 '@playwright/test': ^1.41.2 - react: ^18.2.0 - react-dom: ^18.2.0 + react: 18.3.1 + react-dom: 18.3.1 sass: ^1.3.0 peerDependenciesMeta: '@opentelemetry/api': @@ -7793,8 +7799,8 @@ packages: react-photo-view@1.2.6: resolution: {integrity: sha512-Fq17yxkMIv0oFp7HOJr39HgCZRP6A9K5T5rixJ4flSUYT2OO3V8vNxEExjhIKgIrfmTu+mDnHYEsI9RRWi1JHw==} peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' + react: 18.3.1 + react-dom: 18.3.1 react-redux@7.2.9: resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==} @@ -7812,8 +7818,8 @@ packages: resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': 18.3.1 + react: 18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -7832,8 +7838,8 @@ packages: resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': 18.3.1 + react: 18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -8862,8 +8868,8 @@ packages: resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': 18.3.1 + react: 18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -8913,8 +8919,8 @@ packages: resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': 18.3.1 + react: 18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -12919,6 +12925,8 @@ snapshots: '@webassemblyjs/ast': 1.12.1 '@xtuc/long': 4.2.2 + '@wecom/jssdk@2.2.5': {} + '@xmldom/xmldom@0.8.10': {} '@xtuc/ieee754@1.2.0': {} @@ -14545,7 +14553,11 @@ snapshots: debug: 4.3.5 enhanced-resolve: 5.17.0 eslint: 8.56.0 +<<<<<<< HEAD eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0) +======= + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) +>>>>>>> 29ab002e3 (feat: support wecom sso (#3518)) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 @@ -14557,7 +14569,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -14578,7 +14590,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) hasown: 2.0.2 is-core-module: 2.14.0 is-glob: 4.0.3 @@ -18992,7 +19004,7 @@ snapshots: ts-dedent@2.2.0: {} - ts-jest@29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3): + ts-jest@29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0))(typescript@5.5.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 diff --git a/projects/app/Dockerfile b/projects/app/Dockerfile index 1417f8ae4e66..0ffa7b37aec0 100644 --- a/projects/app/Dockerfile +++ b/projects/app/Dockerfile @@ -81,6 +81,9 @@ COPY --from=builder /app/projects/app/package.json ./package.json COPY ./projects/app/data /app/data RUN chown -R nextjs:nodejs /app/data +# Add tmp directory permission control +RUN mkdir -p /tmp && chmod 666 /tmp + ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV PORT=3000 diff --git a/projects/app/package.json b/projects/app/package.json index 59beab69b81f..6029415bf07f 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "4.8.17", + "version": "4.8.18", "private": false, "scripts": { "dev": "next dev", diff --git a/projects/app/public/imgs/avatar/defaultOrgAvatar.svg b/projects/app/public/imgs/avatar/defaultOrgAvatar.svg new file mode 100644 index 000000000000..38b019594b8e --- /dev/null +++ b/projects/app/public/imgs/avatar/defaultOrgAvatar.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/projects/app/public/imgs/modal/key.svg b/projects/app/public/imgs/modal/key.svg deleted file mode 100644 index 8b86419a3c63..000000000000 --- a/projects/app/public/imgs/modal/key.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/projects/app/public/js/iframe.js b/projects/app/public/js/iframe.js index 1bafd2dfaf2a..c47b581083b7 100644 --- a/projects/app/public/js/iframe.js +++ b/projects/app/public/js/iframe.js @@ -44,6 +44,21 @@ function embedChatbot() { document.body.appendChild(iframe); + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'data-bot-src') { + const newBotSrc = script.getAttribute('data-bot-src'); + if (newBotSrc) { + iframe.src = newBotSrc; + } + } + }); + }); + observer.observe(script, { + attributes: true, + attributeFilter: ['data-bot-src'] + }); + let chatBtnDragged = false; let chatBtnDown = false; let chatBtnMouseX; @@ -96,3 +111,4 @@ function embedChatbot() { document.body.appendChild(ChatBtn); } window.addEventListener('load', embedChatbot); + diff --git a/projects/app/src/components/Layout/index.tsx b/projects/app/src/components/Layout/index.tsx index 2cebee18e501..0fe546023494 100644 --- a/projects/app/src/components/Layout/index.tsx +++ b/projects/app/src/components/Layout/index.tsx @@ -19,6 +19,9 @@ const UpdateInviteModal = dynamic(() => import('@/components/support/user/team/U const NotSufficientModal = dynamic(() => import('@/components/support/wallet/NotSufficientModal')); const SystemMsgModal = dynamic(() => import('@/components/support/user/inform/SystemMsgModal')); const ImportantInform = dynamic(() => import('@/components/support/user/inform/ImportantInform')); +const UpdateNotification = dynamic( + () => import('@/components/support/user/inform/UpdateNotificationModal') +); const pcUnShowLayoutRoute: Record = { '/': true, @@ -48,9 +51,9 @@ export const navbarWidth = '64px'; const Layout = ({ children }: { children: JSX.Element }) => { const router = useRouter(); const { Loading } = useLoading(); - const { loading, feConfigs, isNotSufficientModal } = useSystemStore(); + const { loading, feConfigs, notSufficientModalType } = useSystemStore(); const { isPc } = useSystem(); - const { userInfo } = useUserStore(); + const { userInfo, isUpdateNotification, setIsUpdateNotification } = useUserStore(); const { setUserDefaultLng } = useI18nLng(); const isChatPage = useMemo( @@ -68,6 +71,11 @@ const Layout = ({ children }: { children: JSX.Element }) => { const isHideNavbar = !!pcUnShowLayoutRoute[router.pathname]; + const showUpdateNotification = + isUpdateNotification && + !userInfo?.team.notificationAccount && + !!userInfo?.team.permission.isOwner; + useMount(() => { setUserDefaultLng(); }); @@ -113,8 +121,11 @@ const Layout = ({ children }: { children: JSX.Element }) => { {feConfigs?.isPlus && ( <> {!!userInfo && } - {isNotSufficientModal && } + {notSufficientModalType && } {!!userInfo && } + {showUpdateNotification && ( + setIsUpdateNotification(false)} /> + )} {!!userInfo && importantInforms.length > 0 && ( )} diff --git a/projects/app/src/components/common/Modal/EditResourceModal.tsx b/projects/app/src/components/common/Modal/EditResourceModal.tsx index 95aaf218c3ab..5fd5e0f4f7d9 100644 --- a/projects/app/src/components/common/Modal/EditResourceModal.tsx +++ b/projects/app/src/components/common/Modal/EditResourceModal.tsx @@ -1,17 +1,13 @@ -import React, { useCallback } from 'react'; +import React from 'react'; import { ModalFooter, ModalBody, Input, Button, Box, Textarea, HStack } from '@chakra-ui/react'; import MyModal from '@fastgpt/web/components/common/MyModal/index'; import { useTranslation } from 'next-i18next'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import { useForm } from 'react-hook-form'; -import { compressImgFileAndUpload } from '@/web/common/file/controller'; -import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants'; import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; -import { getErrText } from '@fastgpt/global/common/error/utils'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import Avatar from '@fastgpt/web/components/common/Avatar'; -import { useToast } from '@fastgpt/web/hooks/useToast'; export type EditResourceInfoFormType = { id: string; @@ -31,7 +27,6 @@ const EditResourceModal = ({ onEdit: (data: EditResourceInfoFormType) => any; }) => { const { t } = useTranslation(); - const { toast } = useToast(); const { register, watch, setValue, handleSubmit } = useForm({ defaultValues: defaultForm }); @@ -46,31 +41,14 @@ const EditResourceModal = ({ } ); - const { File, onOpen: onOpenSelectFile } = useSelectFile({ + const { + File, + onOpen: onOpenSelectFile, + onSelectImage + } = useSelectFile({ fileType: '.jpg,.png', multiple: false }); - const onSelectFile = useCallback( - async (e: File[]) => { - const file = e[0]; - if (!file) return; - try { - const src = await compressImgFileAndUpload({ - type: MongoImageTypeEnum.appAvatar, - file, - maxW: 300, - maxH: 300 - }); - setValue('avatar', src); - } catch (err: any) { - toast({ - title: getErrText(err, t('common:common.error.Select avatar failed')), - status: 'warning' - }); - } - }, - [setValue, t, toast] - ); return ( @@ -108,7 +86,15 @@ const EditResourceModal = ({ - + + onSelectImage(e, { + maxH: 300, + maxW: 300, + callback: (e) => setValue('avatar', e) + }) + } + /> ); }; diff --git a/projects/app/src/components/common/folder/SlideCard.tsx b/projects/app/src/components/common/folder/SlideCard.tsx index 90773bed31ab..0f39051c0c69 100644 --- a/projects/app/src/components/common/folder/SlideCard.tsx +++ b/projects/app/src/components/common/folder/SlideCard.tsx @@ -7,7 +7,6 @@ import MyDivider from '@fastgpt/web/components/common/MyDivider'; import { useTranslation } from 'next-i18next'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { PermissionValueType } from '@fastgpt/global/support/permission/type'; -import DefaultPermissionList from '@/components/support/permission/DefaultPerList'; import CollaboratorContextProvider, { MemberManagerInputPropsType } from '../../support/permission/MemberManager/context'; @@ -24,7 +23,6 @@ const FolderSlideCard = ({ deleteTip, onDelete, - defaultPer, managePer, isInheritPermission, resumeInheritPermission, @@ -39,11 +37,6 @@ const FolderSlideCard = ({ deleteTip: string; onDelete: () => void; - defaultPer?: { - value: PermissionValueType; - defaultValue: PermissionValueType; - onChange: (v: PermissionValueType) => Promise; - }; managePer: MemberManagerInputPropsType; isInheritPermission?: boolean; diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx index d2a363db0a22..939d152aac60 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx @@ -104,14 +104,9 @@ const ChatBox = ({ showVoiceIcon = true, showEmptyIntro = false, active = true, - shareId, - outLinkUid, - teamId, - teamToken, onStartChat }: Props) => { const ScrollContainerRef = useRef(null); - const router = useRouter(); const { t } = useTranslation(); const { toast } = useToast(); const { feConfigs } = useSystemStore(); @@ -925,10 +920,6 @@ const ChatBox = ({ isLastChild={index === chatRecords.length - 1} {...{ showVoiceIcon, - shareId, - outLinkUid, - teamId, - teamToken, statusBoxData, questionGuides, onMark: onMark( @@ -1004,17 +995,13 @@ const ChatBox = ({ onCloseUserLike, onMark, onReadUserDislike, - outLinkUid, questionGuides, retryInput, - shareId, showEmpty, showMarkIcon, showVoiceIcon, statusBoxData, t, - teamId, - teamToken, userAvatar, variableList?.length, welcomeText diff --git a/projects/app/src/components/support/apikey/Table.tsx b/projects/app/src/components/support/apikey/Table.tsx index cc048d934847..38e606375812 100644 --- a/projects/app/src/components/support/apikey/Table.tsx +++ b/projects/app/src/components/support/apikey/Table.tsx @@ -250,7 +250,7 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => { {t('common:support.openapi.New api key')} @@ -330,7 +330,7 @@ function EditKeyModal({ return ( diff --git a/projects/app/src/components/support/permission/ConfigPerModal/index.tsx b/projects/app/src/components/support/permission/ConfigPerModal/index.tsx index e5cd606af427..b2575f3369db 100644 --- a/projects/app/src/components/support/permission/ConfigPerModal/index.tsx +++ b/projects/app/src/components/support/permission/ConfigPerModal/index.tsx @@ -44,7 +44,7 @@ const ConfigPerModal = ({ <> diff --git a/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx b/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx deleted file mode 100644 index e113fec3dcba..000000000000 --- a/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx +++ /dev/null @@ -1,308 +0,0 @@ -import { - Flex, - Box, - ModalBody, - Checkbox, - ModalFooter, - Button, - Grid, - HStack -} from '@chakra-ui/react'; -import MyModal from '@fastgpt/web/components/common/MyModal'; -import MyIcon from '@fastgpt/web/components/common/Icon'; -import { useContextSelector } from 'use-context-selector'; -import MyAvatar from '@fastgpt/web/components/common/Avatar'; -import { useMemo, useState } from 'react'; -import PermissionSelect from './PermissionSelect'; -import PermissionTags from './PermissionTags'; -import { CollaboratorContext } from './context'; -import { useUserStore } from '@/web/support/user/useUserStore'; -import { ChevronDownIcon } from '@chakra-ui/icons'; -import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { useTranslation } from 'next-i18next'; -import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; -import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; - -export type AddModalPropsType = { - onClose: () => void; - mode?: 'member' | 'all'; -}; - -function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) { - const { t } = useTranslation(); - const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, myGroups } = useUserStore(); - - const { permissionList, collaboratorList, onUpdateCollaborators, getPerLabelList, permission } = - useContextSelector(CollaboratorContext, (v) => v); - const [searchText, setSearchText] = useState(''); - - const { data: [members = [], groups = []] = [], loading: loadingMembersAndGroups } = useRequest2( - async () => { - if (!userInfo?.team?.teamId) return [[], []]; - return await Promise.all([loadAndGetTeamMembers(true), loadAndGetGroups(true)]); - }, - { - manual: false, - refreshDeps: [userInfo?.team?.teamId] - } - ); - - const filterMembers = useMemo(() => { - return members.filter((item) => { - if (item.tmbId === userInfo?.team?.tmbId) return false; - if (!searchText) return true; - return item.memberName.includes(searchText); - }); - }, [members, searchText, userInfo?.team?.tmbId]); - - const filterGroups = useMemo(() => { - if (mode !== 'all') return []; - return groups.filter((item) => { - if (permission.isOwner) return true; // owner can see all groups - if (myGroups.find((i) => String(i._id) === String(item._id))) return false; - if (!searchText) return true; - return item.name.includes(searchText); - }); - }, [groups, searchText, myGroups, mode, permission]); - - const [selectedMemberIdList, setSelectedMembers] = useState([]); - const [selectedGroupIdList, setSelectedGroupIdList] = useState([]); - const [selectedPermission, setSelectedPermission] = useState(permissionList['read'].value); - const perLabel = useMemo(() => { - return getPerLabelList(selectedPermission).join('、'); - }, [getPerLabelList, selectedPermission]); - - const { runAsync: onConfirm, loading: isUpdating } = useRequest2( - () => - onUpdateCollaborators({ - members: selectedMemberIdList, - groups: selectedGroupIdList, - permission: selectedPermission - }), - { - successToast: t('common:common.Add Success'), - errorToast: 'Error', - onSuccess() { - onClose(); - } - } - ); - - return ( - - - - - setSearchText(e.target.value)} - /> - - - {filterGroups.map((group) => { - const onChange = () => { - setSelectedGroupIdList((state) => { - if (state.includes(group._id)) { - return state.filter((v) => v !== group._id); - } - return [...state, group._id]; - }); - }; - const collaborator = collaboratorList.find((v) => v.groupId === group._id); - return ( - - - - - {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name} - - {!!collaborator && ( - - )} - - ); - })} - {filterMembers.map((member) => { - const onChange = () => { - setSelectedMembers((state) => { - if (state.includes(member.tmbId)) { - return state.filter((v) => v !== member.tmbId); - } - return [...state, member.tmbId]; - }); - }; - const collaborator = collaboratorList.find((v) => v.tmbId === member.tmbId); - return ( - - } - /> - - - {member.memberName} - - {!!collaborator && ( - - )} - - ); - })} - - - - - {t('user:has_chosen') + ': '}{' '} - {selectedMemberIdList.length + selectedGroupIdList.length} - - - {selectedGroupIdList.map((groupId) => { - const onChange = () => { - setSelectedGroupIdList((state) => { - if (state.includes(groupId)) { - return state.filter((v) => v !== groupId); - } - return [...state, groupId]; - }); - }; - const group = groups.find((v) => String(v._id) === groupId); - return ( - - - - {group?.name === DefaultGroupName ? userInfo?.team.teamName : group?.name} - - - - ); - })} - {selectedMemberIdList.map((tmbId) => { - const member = filterMembers.find((v) => v.tmbId === tmbId); - return member ? ( - - setSelectedMembers(selectedMemberIdList.filter((v) => v !== tmbId)) - } - > - - - {member.memberName} - - - - ) : null; - })} - - - - - - - {t(perLabel as any)} - - - } - onChange={(v) => setSelectedPermission(v)} - /> - - - - ); -} - -export default AddMemberModal; diff --git a/projects/app/src/components/support/permission/MemberManager/ManageModal.tsx b/projects/app/src/components/support/permission/MemberManager/ManageModal.tsx index 37dc4260ec02..b7e95d1a2dd4 100644 --- a/projects/app/src/components/support/permission/MemberManager/ManageModal.tsx +++ b/projects/app/src/components/support/permission/MemberManager/ManageModal.tsx @@ -1,19 +1,19 @@ -import { ModalBody, Table, TableContainer, Tbody, Th, Thead, Tr, Td, Flex } from '@chakra-ui/react'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import { Flex, ModalBody, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'; +import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; +import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; +import Avatar from '@fastgpt/web/components/common/Avatar'; +import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import Loading from '@fastgpt/web/components/common/MyLoading'; import MyModal from '@fastgpt/web/components/common/MyModal'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { useTranslation } from 'next-i18next'; import React from 'react'; import { useContextSelector } from 'use-context-selector'; import PermissionSelect from './PermissionSelect'; import PermissionTags from './PermissionTags'; -import Avatar from '@fastgpt/web/components/common/Avatar'; import { CollaboratorContext } from './context'; -import MyIcon from '@fastgpt/web/components/common/Icon'; -import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { useUserStore } from '@/web/support/user/useUserStore'; -import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; -import Loading from '@fastgpt/web/components/common/MyLoading'; -import { useTranslation } from 'next-i18next'; -import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; -import { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; export type ManageModalProps = { onClose: () => void; }; @@ -65,7 +65,7 @@ function ManageModal({ onClose }: ManageModalProps) { > - + {item.name === DefaultGroupName ? userInfo?.team.teamName : item.name} @@ -85,14 +85,20 @@ function ManageModal({ onClose }: ManageModalProps) { onUpdate({ members: item.tmbId ? [item.tmbId] : undefined, groups: item.groupId ? [item.groupId] : undefined, + orgs: item.orgId ? [item.orgId] : undefined, permission }); }} onDelete={() => { onDelete({ tmbId: item.tmbId, - groupId: item.groupId - } as RequireOnlyOne<{ tmbId: string; groupId: string }>); + groupId: item.groupId, + orgId: item.orgId + } as RequireOnlyOne<{ + tmbId: string; + groupId: string; + orgId: string; + }>); }} /> )} diff --git a/projects/app/src/components/support/permission/MemberManager/MemberListCard.tsx b/projects/app/src/components/support/permission/MemberManager/MemberListCard.tsx index 477c777f7f29..fe85f36a3eaa 100644 --- a/projects/app/src/components/support/permission/MemberManager/MemberListCard.tsx +++ b/projects/app/src/components/support/permission/MemberManager/MemberListCard.tsx @@ -1,13 +1,13 @@ -import { Box, BoxProps, Flex } from '@chakra-ui/react'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import { Box, type BoxProps, Flex } from '@chakra-ui/react'; +import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; +import Avatar from '@fastgpt/web/components/common/Avatar'; import MyBox from '@fastgpt/web/components/common/MyBox'; +import Tag, { type TagProps } from '@fastgpt/web/components/common/Tag'; +import { useTranslation } from 'next-i18next'; import React from 'react'; import { useContextSelector } from 'use-context-selector'; import { CollaboratorContext } from './context'; -import Tag, { TagProps } from '@fastgpt/web/components/common/Tag'; -import Avatar from '@fastgpt/web/components/common/Avatar'; -import { useTranslation } from 'next-i18next'; -import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; -import { useUserStore } from '@/web/support/user/useUserStore'; export type MemberListCardProps = BoxProps & { tagStyle?: Omit }; @@ -31,12 +31,12 @@ const MemberListCard = ({ tagStyle, ...props }: MemberListCardProps) => { {collaboratorList?.map((member) => { return ( - + {member.name === DefaultGroupName ? userInfo?.team.teamName : member.name} diff --git a/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx new file mode 100644 index 000000000000..e51049b33a1a --- /dev/null +++ b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx @@ -0,0 +1,512 @@ +import { useUserStore } from '@/web/support/user/useUserStore'; +import { ChevronDownIcon } from '@chakra-ui/icons'; +import { + Box, + Button, + Checkbox, + Flex, + Grid, + HStack, + ModalBody, + ModalFooter, + Tag, + Text +} from '@chakra-ui/react'; +import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; +import MyAvatar from '@fastgpt/web/components/common/Avatar'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; +import MyModal from '@fastgpt/web/components/common/MyModal'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { useTranslation } from 'next-i18next'; +import { useMemo, useRef, useState } from 'react'; +import PermissionSelect from './PermissionSelect'; +import PermissionTags from './PermissionTags'; +import { + DEFAULT_ORG_AVATAR, + DEFAULT_TEAM_AVATAR, + DEFAULT_USER_AVATAR +} from '@fastgpt/global/common/system/constants'; +import Path from '@/components/common/folder/Path'; +import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant'; +import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; +import { OrgType } from '@fastgpt/global/support/user/team/org/type'; +import { useContextSelector } from 'use-context-selector'; +import { CollaboratorContext } from './context'; + +const HoverBoxStyle = { + bgColor: 'myGray.50', + cursor: 'pointer' +}; + +function MemberModal({ + onClose, + addPermissionOnly: addOnly = false +}: { + onClose: () => void; + addPermissionOnly?: boolean; +}) { + const { t } = useTranslation(); + const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, loadAndGetOrgs } = useUserStore(); + + const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList); + + const [searchText, setSearchText] = useState(''); + const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>(); + + const { data: [members = [], groups = [], orgs = []] = [], loading: loadingMembersAndGroups } = + useRequest2( + async () => { + if (!userInfo?.team?.teamId) return [[], []]; + return Promise.all([ + loadAndGetTeamMembers(true), + loadAndGetGroups(true), + loadAndGetOrgs(true) + ]); + }, + { + manual: false, + refreshDeps: [userInfo?.team?.teamId] + } + ); + + const [parentPath, setParentPath] = useState(''); + const paths = useMemo(() => { + const splitPath = parentPath.split('/').filter(Boolean); + return splitPath + .map((id) => { + const org = orgs.find((org) => org.pathId === id)!; + + if (org.path === '') return; + + return { + parentId: getOrgChildrenPath(org), + parentName: org.name + }; + }) + .filter(Boolean) as ParentTreePathItemType[]; + }, [parentPath, orgs]); + + const [selectedOrgIdList, setSelectedOrgIdList] = useState([]); + const currentOrg = useMemo(() => { + const splitPath = parentPath.split('/'); + const currentOrgId = splitPath[splitPath.length - 1]; + if (!currentOrgId) return; + + return orgs.find((org) => org.pathId === currentOrgId); + }, [orgs, parentPath]); + const filterOrgs: (OrgType & { count?: number })[] = useMemo(() => { + if (searchText) return orgs.filter((item) => item.name.includes(searchText)); + if (!searchText && filterClass !== 'org') return []; + if (parentPath === '') { + setParentPath(`/${orgs[0].pathId}`); + return []; + } + return orgs + .filter((org) => org.path === parentPath) + .map((item) => ({ + ...item, + count: + item.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(item)).length + })); + }, [orgs, searchText, filterClass, parentPath]); + + const [selectedMemberIdList, setSelectedMembers] = useState([]); + const filterMembers = useMemo(() => { + if (searchText) return members.filter((item) => item.memberName.includes(searchText)); + if (!searchText && filterClass !== 'member' && filterClass !== 'org') return []; + + if (currentOrg && filterClass === 'org') { + return members.filter((item) => currentOrg.members.find((v) => v.tmbId === item.tmbId)); + } + + return members; + }, [members, searchText, filterClass, currentOrg]); + + const [selectedGroupIdList, setSelectedGroupIdList] = useState([]); + const filterGroups = useMemo(() => { + if (searchText) return groups.filter((item) => item.name.includes(searchText)); + if (!searchText && filterClass !== 'group') return []; + + return groups; + }, [groups, searchText, filterClass]); + + const permissionList = useContextSelector(CollaboratorContext, (v) => v.permissionList); + const getPerLabelList = useContextSelector(CollaboratorContext, (v) => v.getPerLabelList); + const [selectedPermission, setSelectedPermission] = useState( + permissionList?.read?.value + ); + const perLabel = useMemo(() => { + if (selectedPermission === undefined) return ''; + return getPerLabelList(selectedPermission!).join('、'); + }, [getPerLabelList, selectedPermission]); + + const onUpdateCollaborators = useContextSelector( + CollaboratorContext, + (v) => v.onUpdateCollaborators + ); + const { runAsync: onConfirm, loading: isUpdating } = useRequest2( + () => + onUpdateCollaborators({ + members: selectedMemberIdList, + groups: selectedGroupIdList, + orgs: selectedOrgIdList, + permission: selectedPermission! + }), + { + successToast: t('common:common.Add Success'), + onSuccess() { + onClose(); + } + } + ); + + const entryList = useRef([ + { label: t('user:team.group.members'), icon: DEFAULT_USER_AVATAR, value: 'member' }, + { label: t('user:team.org.org'), icon: DEFAULT_ORG_AVATAR, value: 'org' }, + { label: t('user:team.group.group'), icon: DEFAULT_TEAM_AVATAR, value: 'group' } + ]); + + const selectedList = useMemo(() => { + const selectedOrgs = orgs.filter((org) => selectedOrgIdList.includes(org._id)); + const selectedGroups = groups.filter((group) => selectedGroupIdList.includes(group._id)); + const selectedMembers = members.filter((member) => selectedMemberIdList.includes(member.tmbId)); + + return [ + ...selectedOrgs.map((item) => ({ + id: `org-${item._id}`, + avatar: item.avatar, + name: item.name, + onDelete: () => setSelectedOrgIdList(selectedOrgIdList.filter((v) => v !== item._id)) + })), + ...selectedGroups.map((item) => ({ + id: `group-${item._id}`, + avatar: item.avatar, + name: item.name === DefaultGroupName ? userInfo?.team.teamName : item.name, + onDelete: () => setSelectedGroupIdList(selectedGroupIdList.filter((v) => v !== item._id)) + })), + ...selectedMembers.map((item) => ({ + id: `member-${item.tmbId}`, + avatar: item.avatar, + name: item.memberName, + onDelete: () => setSelectedMembers(selectedMemberIdList.filter((v) => v !== item.tmbId)) + })) + ]; + }, [ + orgs, + groups, + members, + selectedOrgIdList, + selectedGroupIdList, + selectedMemberIdList, + userInfo?.team.teamName + ]); + + return ( + + + + + setSearchText(e.target.value)} + /> + + + {!searchText && !filterClass && ( + <> + {entryList.current.map((item) => { + return ( + setFilterClass(item.value as any)} + > + + + {item.label} + + + + ); + })} + + )} + + {/* Path */} + {!searchText && filterClass && ( + + { + if (parentId === '') { + setFilterClass(undefined); + setParentPath(''); + } else if ( + parentId === 'member' || + parentId === 'org' || + parentId === 'group' + ) { + setFilterClass(parentId); + setParentPath(''); + } else { + setParentPath(parentId); + } + }} + rootName={t('common:common.Team')} + /> + + )} + + + {filterMembers.map((member) => { + const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId); + const disabled = addOnly && collaborator !== undefined; + const onChange = () => { + if (disabled) return; + setSelectedMembers((state) => { + if (state.includes(member.tmbId)) { + return state.filter((v) => v !== member.tmbId); + } + return [...state, member.tmbId]; + }); + }; + return ( + + + + + {member.memberName} + + + + ); + })} + {filterOrgs.map((org) => { + const collaborator = collaboratorList?.find((v) => v.orgId === org._id); + const disabled = addOnly && collaborator !== undefined; + const onChange = () => { + if (disabled) return; + setSelectedOrgIdList((state) => { + if (state.includes(org._id)) { + return state.filter((v) => v !== org._id); + } + return [...state, org._id]; + }); + }; + return ( + + + + + {org.name} + {org.count && ( + + {org.count} + + )} + + + {org.count && ( + { + e.stopPropagation(); + setParentPath(getOrgChildrenPath(org)); + }} + /> + )} + + ); + })} + {filterGroups.map((group) => { + const collaborator = collaboratorList?.find((v) => v.groupId === group._id); + const disabled = addOnly && collaborator !== undefined; + const onChange = () => { + if (disabled) return; + setSelectedGroupIdList((state) => { + if (state.includes(group._id)) { + return state.filter((v) => v !== group._id); + } + return [...state, group._id]; + }); + }; + return ( + + + + + {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name} + + + + ); + })} + + + + + + + {`${t('user:has_chosen')}: `} + {selectedMemberIdList.length + selectedGroupIdList.length + selectedOrgIdList.length} + + + {selectedList.map((item) => { + return ( + + + + {item.name} + + + + ); + })} + + + + + + {!addOnly && !!permissionList && ( + + {t(perLabel as any)} + + + } + onChange={(v) => setSelectedPermission(v)} + /> + )} + {addOnly && ( + + + {t('user:permission_add_tip')} + + )} + + + + ); +} + +export default MemberModal; diff --git a/projects/app/src/components/support/permission/MemberManager/PermissionSelect.tsx b/projects/app/src/components/support/permission/MemberManager/PermissionSelect.tsx index 5b8562b6502a..3cfd569632c6 100644 --- a/projects/app/src/components/support/permission/MemberManager/PermissionSelect.tsx +++ b/projects/app/src/components/support/permission/MemberManager/PermissionSelect.tsx @@ -49,13 +49,16 @@ function PermissionSelect({ onDelete }: PermissionSelectProps) { const { t } = useTranslation(); - const { permission, permissionList } = useContextSelector(CollaboratorContext, (v) => v); const ref = useRef(null); const closeTimer = useRef(); + const { permission, permissionList } = useContextSelector(CollaboratorContext, (v) => v); + const [isOpen, setIsOpen] = useState(false); const permissionSelectList = useMemo(() => { + if (!permissionList) return { singleCheckBoxList: [], multipleCheckBoxList: [] }; + const list = Object.entries(permissionList).map(([_, value]) => { return { name: value.name, @@ -77,6 +80,8 @@ function PermissionSelect({ }; }, [permission.isOwner, permissionList]); const selectedSingleValue = useMemo(() => { + if (!permissionList) return undefined; + const per = new Permission({ per: value }); if (per.hasManagePer) return permissionList['manage'].value; @@ -107,7 +112,7 @@ function PermissionSelect({ } }); - return ( + return selectedSingleValue !== undefined ? ( - ); + ) : null; } export default React.memo(PermissionSelect); diff --git a/projects/app/src/components/support/permission/MemberManager/PermissionTags.tsx b/projects/app/src/components/support/permission/MemberManager/PermissionTags.tsx index 7d9f7de16def..5bda6929c84e 100644 --- a/projects/app/src/components/support/permission/MemberManager/PermissionTags.tsx +++ b/projects/app/src/components/support/permission/MemberManager/PermissionTags.tsx @@ -7,12 +7,15 @@ import { CollaboratorContext } from './context'; import { useTranslation } from 'next-i18next'; export type PermissionTagsProp = { - permission: PermissionValueType; + permission?: PermissionValueType; }; function PermissionTags({ permission }: PermissionTagsProp) { const { getPerLabelList } = useContextSelector(CollaboratorContext, (v) => v); const { t } = useTranslation(); + + if (permission === undefined) return null; + const perTagList = getPerLabelList(permission); return ( diff --git a/projects/app/src/components/support/permission/MemberManager/context.tsx b/projects/app/src/components/support/permission/MemberManager/context.tsx index 5ec06bd935af..683830b1538b 100644 --- a/projects/app/src/components/support/permission/MemberManager/context.tsx +++ b/projects/app/src/components/support/permission/MemberManager/context.tsx @@ -1,32 +1,37 @@ import { useDisclosure } from '@chakra-ui/react'; -import { +import type { CollaboratorItemType, UpdateClbPermissionProps } from '@fastgpt/global/support/permission/collaborator'; import { PermissionList } from '@fastgpt/global/support/permission/constant'; import { Permission } from '@fastgpt/global/support/permission/controller'; -import { PermissionListType, PermissionValueType } from '@fastgpt/global/support/permission/type'; -import { ReactNode, useCallback } from 'react'; +import type { + PermissionListType, + PermissionValueType +} from '@fastgpt/global/support/permission/type'; +import { type ReactNode, useCallback } from 'react'; import { createContext } from 'use-context-selector'; import dynamic from 'next/dynamic'; -import MemberListCard, { MemberListCardProps } from './MemberListCard'; +import MemberListCard, { type MemberListCardProps } from './MemberListCard'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useI18n } from '@/web/context/I18n'; -import { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; -const AddMemberModal = dynamic(() => import('./AddMemberModal')); +import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; + +const MemberModal = dynamic(() => import('./MemberModal')); const ManageModal = dynamic(() => import('./ManageModal')); export type MemberManagerInputPropsType = { permission: Permission; onGetCollaboratorList: () => Promise; - permissionList: PermissionListType; + permissionList?: PermissionListType; onUpdateCollaborators: (props: UpdateClbPermissionProps) => Promise; - onDelOneCollaborator: (props: RequireOnlyOne<{ tmbId: string; groupId: string }>) => Promise; + onDelOneCollaborator: ( + props: RequireOnlyOne<{ tmbId: string; groupId: string; orgId: string }> + ) => Promise; refreshDeps?: any[]; - mode?: 'member' | 'all'; }; export type MemberManagerPropsType = MemberManagerInputPropsType & { @@ -46,19 +51,19 @@ type CollaboratorContextType = MemberManagerPropsType & {}; export const CollaboratorContext = createContext({ collaboratorList: [], permissionList: PermissionList, - onUpdateCollaborators: function () { + onUpdateCollaborators: () => { throw new Error('Function not implemented.'); }, - onDelOneCollaborator: function () { + onDelOneCollaborator: () => { throw new Error('Function not implemented.'); }, - getPerLabelList: function (): string[] { + getPerLabelList: (): string[] => { throw new Error('Function not implemented.'); }, - refetchCollaboratorList: function (): void { + refetchCollaboratorList: (): void => { throw new Error('Function not implemented.'); }, - onGetCollaboratorList: function (): Promise { + onGetCollaboratorList: (): Promise => { throw new Error('Function not implemented.'); }, isFetchingCollaborator: false, @@ -76,19 +81,20 @@ const CollaboratorContextProvider = ({ refreshDeps = [], isInheritPermission, hasParent, - mode = 'member' + addPermissionOnly }: MemberManagerInputPropsType & { children: (props: ChildrenProps) => ReactNode; refetchResource?: () => void; isInheritPermission?: boolean; hasParent?: boolean; + addPermissionOnly?: boolean; }) => { const onUpdateCollaboratorsThen = async (props: UpdateClbPermissionProps) => { await onUpdateCollaborators(props); refetchCollaboratorList(); }; const onDelOneCollaboratorThen = async ( - props: RequireOnlyOne<{ tmbId: string; groupId: string }> + props: RequireOnlyOne<{ tmbId: string; groupId: string; orgId: string }> ) => { await onDelOneCollaborator(props); refetchCollaboratorList(); @@ -116,6 +122,8 @@ const CollaboratorContextProvider = ({ const getPerLabelList = useCallback( (per: PermissionValueType) => { + if (!permissionList) return []; + const Per = new Permission({ per }); const labels: string[] = []; @@ -123,7 +131,7 @@ const CollaboratorContextProvider = ({ labels.push(permissionList['manage'].name); } else if (Per.hasWritePer) { labels.push(permissionList['write'].name); - } else { + } else if (Per.hasReadPer) { labels.push(permissionList['read'].name); } @@ -198,12 +206,12 @@ const CollaboratorContextProvider = ({ MemberListCard })} {isOpenAddMember && ( - { onCloseAddMember(); refetchResource?.(); }} - mode={mode} + addPermissionOnly={addPermissionOnly} /> )} {isOpenManageModal && ( diff --git a/projects/app/src/pages/account/info/components/UpdateNotificationModal.tsx b/projects/app/src/components/support/user/inform/UpdateNotificationModal.tsx similarity index 79% rename from projects/app/src/pages/account/info/components/UpdateNotificationModal.tsx rename to projects/app/src/components/support/user/inform/UpdateNotificationModal.tsx index 5adbb505fa84..e6e220196b73 100644 --- a/projects/app/src/pages/account/info/components/UpdateNotificationModal.tsx +++ b/projects/app/src/components/support/user/inform/UpdateNotificationModal.tsx @@ -38,8 +38,8 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => { initUserInfo(); onClose(); }, - successToast: t('account_info:bind_notification_success'), - errorToast: t('account_info:bind_notification_error') + successToast: t('common:support.user.info.bind_notification_success'), + errorToast: t('common:support.user.info.bind_notification_error') } ); @@ -49,9 +49,9 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => { ?.map((item) => { switch (item) { case 'email': - return t('account_info:email_label'); + return t('common:support.user.login.Email'); case 'phone': - return t('account_info:phone_label'); + return t('common:support.user.login.Phone number'); } }) .join('/'); @@ -62,16 +62,16 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => { isOpen iconSrc="common/settingLight" w={'32rem'} - title={t('account_info:notification_receiving_hint')} + title={t('common:support.user.info.notification_receiving_hint')} > - {t('account_info:bind_notification_hint')} + {t('common:support.user.info.bind_notification_hint')} - {t('account_info:user_account')} + {t('common:user.Account')} void }) => { > - {t('account_info:verification_code')} + {t('common:support.user.info.verification_code')} @@ -93,14 +93,14 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => { diff --git a/projects/app/src/components/support/wallet/NotSufficientModal/index.tsx b/projects/app/src/components/support/wallet/NotSufficientModal/index.tsx index 94721c957757..fa4590428c93 100644 --- a/projects/app/src/components/support/wallet/NotSufficientModal/index.tsx +++ b/projects/app/src/components/support/wallet/NotSufficientModal/index.tsx @@ -1,35 +1,153 @@ -import React from 'react'; +import React, { useMemo, useState } from 'react'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { useTranslation } from 'next-i18next'; -import { Button, ModalBody, ModalFooter } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import { useSystemStore } from '@/web/common/system/useSystemStore'; +import { Box, Button, Flex, ModalBody, ModalFooter, useDisclosure } from '@chakra-ui/react'; +import { NotSufficientModalType, useSystemStore } from '@/web/common/system/useSystemStore'; +import ExtraPlan from '@/pages/price/components/ExtraPlan'; +import StandardPlan from '@/pages/price/components/Standard'; +import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs'; +import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants'; +import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; +import { useMount } from 'ahooks'; -const NotSufficientModal = () => { +const NotSufficientModal = ({ type }: { type: NotSufficientModalType }) => { const { t } = useTranslation(); - const router = useRouter(); - const { setIsNotSufficientModal } = useSystemStore(); + const { setNotSufficientModalType } = useSystemStore(); - const onClose = () => setIsNotSufficientModal(false); + const onClose = () => setNotSufficientModalType(undefined); + + const { + isOpen: isRechargeModalOpen, + onOpen: onRechargeModalOpen, + onClose: onRechargeModalClose + } = useDisclosure(); + + const textMap = { + [TeamErrEnum.aiPointsNotEnough]: t('common:support.wallet.Not sufficient'), + [TeamErrEnum.datasetSizeNotEnough]: t('common:support.wallet.Dataset_not_sufficient'), + [TeamErrEnum.datasetAmountNotEnough]: t('common:support.wallet.Dataset_amount_not_sufficient'), + [TeamErrEnum.teamMemberOverSize]: t('common:support.wallet.Team_member_over_size'), + [TeamErrEnum.appAmountNotEnough]: t('common:support.wallet.App_amount_not_sufficient') + }; return ( - - {t('common:support.wallet.Not sufficient')} - - - + + + + + {isRechargeModalOpen && ( + + )} + + ); +}; + +export default NotSufficientModal; + +const RechargeModal = ({ + onClose, + onPaySuccess +}: { + onClose: () => void; + onPaySuccess: () => void; +}) => { + const { t } = useTranslation(); + const { teamPlanStatus, initTeamPlanStatus } = useUserStore(); + + useMount(() => { + initTeamPlanStatus(); + }); + + const planName = useMemo(() => { + if (!teamPlanStatus?.standard?.currentSubLevel) return ''; + return standardSubLevelMap[teamPlanStatus.standard.currentSubLevel].label; + }, [teamPlanStatus?.standard?.currentSubLevel]); + + const [tab, setTab] = useState<'standard' | 'extra'>('standard'); + + return ( + + + + + {t('common:support.wallet.subscription.Current plan')} + + + {t(planName as any)} + + + + + + {t('common:info.resource')} + + + {`${t('common:support.user.team.Dataset usage')}:`} + {`${teamPlanStatus?.usedDatasetSize} / ${teamPlanStatus?.datasetMaxSize || t('account_info:unlimited')}`} + {`${t('common:support.wallet.subscription.AI points usage')}:`} + {`${Math.round(teamPlanStatus?.usedPoints || 0)} / ${teamPlanStatus?.totalPoints || t('account_info:unlimited')}`} + + + + { + setTab(e as 'standard' | 'extra'); }} + /> + + - {t('common:support.wallet.To read plan')} - - + {tab === 'standard' ? ( + + ) : ( + + )} + + ); }; - -export default NotSufficientModal; diff --git a/projects/app/src/components/support/wallet/QRCodePayModal.tsx b/projects/app/src/components/support/wallet/QRCodePayModal.tsx index 013a91b93cf4..6b3fe5bd5ad7 100644 --- a/projects/app/src/components/support/wallet/QRCodePayModal.tsx +++ b/projects/app/src/components/support/wallet/QRCodePayModal.tsx @@ -1,5 +1,5 @@ import MyModal from '@fastgpt/web/components/common/MyModal'; -import React, { useEffect, useRef } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import { useTranslation } from 'next-i18next'; import { Box, ModalBody } from '@chakra-ui/react'; import { checkBalancePayResult } from '@/web/support/wallet/bill/api'; @@ -7,6 +7,8 @@ import { useToast } from '@fastgpt/web/hooks/useToast'; import { useRouter } from 'next/router'; import { getErrText } from '@fastgpt/global/common/error/utils'; import LightTip from '@fastgpt/web/components/common/LightTip'; +import Script from 'next/script'; +import { getWebReqUrl } from '@fastgpt/web/common/system/utils'; export type QRPayProps = { readPrice: number; @@ -23,25 +25,25 @@ const QRCodePayModal = ({ billId, onSuccess }: QRPayProps & { tip?: string; onSuccess?: () => any }) => { - const router = useRouter(); const { t } = useTranslation(); const { toast } = useToast(); const dom = useRef(null); + const drawCode = useCallback(() => { + if (dom.current && window.QRCode && !dom.current.innerHTML) { + new window.QRCode(dom.current, { + text: codeUrl, + width: qrCodeSize, + height: qrCodeSize, + colorDark: '#000000', + colorLight: '#ffffff', + correctLevel: window.QRCode.CorrectLevel.H + }); + } + }, [codeUrl]); + useEffect(() => { let timer: NodeJS.Timeout; - const drawCode = () => { - if (dom.current && window.QRCode && !dom.current.innerHTML) { - new window.QRCode(dom.current, { - text: codeUrl, - width: qrCodeSize, - height: qrCodeSize, - colorDark: '#000000', - colorLight: '#ffffff', - correctLevel: window.QRCode.CorrectLevel.H - }); - } - }; const check = async () => { try { const res = await checkBalancePayResult(billId); @@ -52,9 +54,6 @@ const QRCodePayModal = ({ title: res, status: 'success' }); - setTimeout(() => { - router.reload(); - }, 1000); return; } catch (error) { toast({ @@ -73,18 +72,26 @@ const QRCodePayModal = ({ check(); return () => clearTimeout(timer); - }, [billId, onSuccess, toast]); + }, [billId, drawCode, onSuccess, toast]); return ( - - - {tip && } - - - {t('common:pay.wechat', { price: readPrice })} - - - + <> + + + + + {tip && } + + + {t('common:pay.wechat', { price: readPrice })} + + + + ); }; diff --git a/projects/app/src/pages/account/components/AccountContainer.tsx b/projects/app/src/pages/account/components/AccountContainer.tsx index 8d75c53335b4..cdbf1445c622 100644 --- a/projects/app/src/pages/account/components/AccountContainer.tsx +++ b/projects/app/src/pages/account/components/AccountContainer.tsx @@ -94,7 +94,7 @@ const AccountContainer = ({ ...(userInfo?.team?.permission.hasManagePer ? [ { - icon: 'support/outlink/apikeyLight', + icon: 'key', label: t('account:api_key'), value: TabEnum.apikey } diff --git a/projects/app/src/pages/account/components/TeamSelector.tsx b/projects/app/src/pages/account/components/TeamSelector.tsx index 27ef889530c1..c3cd03c2a6c1 100644 --- a/projects/app/src/pages/account/components/TeamSelector.tsx +++ b/projects/app/src/pages/account/components/TeamSelector.tsx @@ -75,7 +75,7 @@ const TeamSelector = ({ key={'manage'} alignItems={'center'} borderRadius={'md'} - cursor={'default'} + cursor={'pointer'} gap={3} onClick={() => router.push('/account/team')} > diff --git a/projects/app/src/pages/account/info/components/UpdatePswModal.tsx b/projects/app/src/pages/account/info/components/UpdatePswModal.tsx index e8518e926be0..092625db5610 100644 --- a/projects/app/src/pages/account/info/components/UpdatePswModal.tsx +++ b/projects/app/src/pages/account/info/components/UpdatePswModal.tsx @@ -3,8 +3,10 @@ import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/rea import MyModal from '@fastgpt/web/components/common/MyModal'; import { useTranslation } from 'next-i18next'; import { useForm } from 'react-hook-form'; -import { useRequest } from '@fastgpt/web/hooks/useRequest'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { updatePasswordByOld } from '@/web/support/user/api'; +import { PasswordRule } from '@/web/support/user/login/constants'; +import { useToast } from '@fastgpt/web/hooks/useToast'; type FormType = { oldPsw: string; @@ -14,7 +16,9 @@ type FormType = { const UpdatePswModal = ({ onClose }: { onClose: () => void }) => { const { t } = useTranslation(); - const { register, handleSubmit } = useForm({ + const { toast } = useToast(); + + const { register, handleSubmit, getValues } = useForm({ defaultValues: { oldPsw: '', newPsw: '', @@ -22,19 +26,25 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => { } }); - const { mutate: onSubmit, isLoading } = useRequest({ - mutationFn: (data: FormType) => { - if (data.newPsw !== data.confirmPsw) { - return Promise.reject(t('account_info:password_mismatch')); - } - return updatePasswordByOld(data); - }, + const { runAsync: onSubmit, loading: isLoading } = useRequest2(updatePasswordByOld, { onSuccess() { onClose(); }, successToast: t('account_info:password_update_success'), errorToast: t('account_info:password_update_error') }); + const onSubmitErr = (err: Record) => { + const val = Object.values(err)[0]; + if (!val) return; + if (val.message) { + toast({ + status: 'warning', + title: val.message, + duration: 3000, + isClosable: true + }); + } + }; return ( void }) => { > - {t('account_info:old_password') + ':'} + + {t('account_info:old_password') + ':'} + - {t('account_info:new_password') + ':'} + + {t('account_info:new_password') + ':'} + - {t('account_info:confirm_password') + ':'} + + {t('account_info:confirm_password') + ':'} + (getValues('newPsw') === val ? true : t('user:password.not_match')) })} > @@ -81,7 +96,7 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => { - diff --git a/projects/app/src/pages/account/info/index.tsx b/projects/app/src/pages/account/info/index.tsx index d16b3ea1362a..48c71cc31277 100644 --- a/projects/app/src/pages/account/info/index.tsx +++ b/projects/app/src/pages/account/info/index.tsx @@ -9,8 +9,7 @@ import { Link, Progress, Grid, - BoxProps, - FlexProps + BoxProps } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; import { UserUpdateParams } from '@/types/user'; @@ -20,7 +19,6 @@ 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 { compressImgFileAndUpload } from '@/web/common/file/controller'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useTranslation } from 'next-i18next'; import Avatar from '@fastgpt/web/components/common/Avatar'; @@ -29,7 +27,6 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools'; import { putUpdateMemberName } from '@/web/support/user/team/api'; import { getDocPath } from '@/web/common/system/doc'; -import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants'; import { StandardSubLevelEnum, standardSubLevelMap @@ -49,7 +46,9 @@ import TeamSelector from '../components/TeamSelector'; const StandDetailModal = dynamic(() => import('./components/standardDetailModal'), { ssr: false }); const ConversionModal = dynamic(() => import('./components/ConversionModal')); const UpdatePswModal = dynamic(() => import('./components/UpdatePswModal')); -const UpdateNotification = dynamic(() => import('./components/UpdateNotificationModal')); +const UpdateNotification = dynamic( + () => import('@/components/support/user/inform/UpdateNotificationModal') +); const CommunityModal = dynamic(() => import('@/components/CommunityModal')); const ModelPriceModal = dynamic(() => @@ -131,7 +130,11 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => { onClose: onCloseUpdateNotification, onOpen: onOpenUpdateNotification } = useDisclosure(); - const { File, onOpen: onOpenSelectFile } = useSelectFile({ + const { + File, + onOpen: onOpenSelectFile, + onSelectImage + } = useSelectFile({ fileType: '.jpg,.png', multiple: false }); @@ -151,32 +154,6 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => { [reset, t, toast, updateUserInfo] ); - const onSelectFile = useCallback( - async (e: File[]) => { - const file = e[0]; - if (!file || !userInfo) return; - try { - const src = await compressImgFileAndUpload({ - type: MongoImageTypeEnum.userAvatar, - file, - maxW: 300, - maxH: 300 - }); - - onclickSave({ - ...userInfo, - avatar: src - }); - } catch (err: any) { - toast({ - title: typeof err === 'string' ? err : t('account_info:avatar_selection_exception'), - status: 'warning' - }); - } - }, - [onclickSave, t, toast, userInfo] - ); - const labelStyles: BoxProps = { flex: '0 0 80px', fontSize: 'sm', @@ -329,7 +306,21 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => { )} {isOpenUpdatePsw && } {isOpenUpdateNotification && } - + + onSelectImage(e, { + maxW: 300, + maxH: 300, + callback: (src) => { + if (!userInfo) return; + onclickSave({ + ...userInfo, + avatar: src + }); + } + }) + } + /> ); }; diff --git a/projects/app/src/pages/account/model/components/DefaultModal.tsx b/projects/app/src/pages/account/model/components/DefaultModal.tsx new file mode 100644 index 000000000000..2568c9500468 --- /dev/null +++ b/projects/app/src/pages/account/model/components/DefaultModal.tsx @@ -0,0 +1,84 @@ +import React, { useMemo, useState } from 'react'; +import MyModal from '@fastgpt/web/components/common/MyModal'; +import { useTranslation } from 'next-i18next'; +import { Box, Flex, ModalBody } from '@chakra-ui/react'; +import { MultipleRowArraySelect } from '@fastgpt/web/components/common/MySelect/MultipleRowSelect'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import { ModelProviderList } from '@fastgpt/global/core/ai/provider'; +import Avatar from '@fastgpt/web/components/common/Avatar'; +import { HUGGING_FACE_ICON } from '@fastgpt/global/common/system/constants'; +import { getModelFromList } from '@fastgpt/global/core/ai/model'; + +const DefaultModal = ({ onClose }: { onClose: () => void }) => { + const { t } = useTranslation(); + const { llmModelList, vectorModelList, whisperModel, audioSpeechModelList, reRankModelList } = + useSystemStore(); + const [value, setValue] = useState([]); + + const modelList = useMemo(() => { + return [ + ...llmModelList, + ...vectorModelList, + ...audioSpeechModelList, + ...reRankModelList, + whisperModel + ].map((item) => ({ + provider: item.provider, + name: item.name, + model: item.model + })); + }, [llmModelList, vectorModelList, whisperModel, audioSpeechModelList, reRankModelList]); + + const selectorList = useMemo(() => { + const renderList = ModelProviderList.map<{ + label: React.JSX.Element; + value: string; + children: { label: string | React.ReactNode; value: string }[]; + }>((provider) => ({ + label: ( + + + {t(provider.name as any)} + + ), + value: provider.id, + children: [] + })); + + for (const item of modelList) { + const modelData = getModelFromList(modelList, item.model); + const provider = + renderList.find((item) => item.value === (modelData?.provider || 'Other')) ?? + renderList[renderList.length - 1]; + + provider.children.push({ + label: modelData.name, + value: modelData.model + }); + } + + return renderList.filter((item) => item.children.length > 0); + }, [modelList, t]); + + console.log(selectorList); + + return ( + + 11 + + ); +}; + +export default DefaultModal; diff --git a/projects/app/src/pages/account/model/index.tsx b/projects/app/src/pages/account/model/index.tsx index 6af07f08773e..c0c1b5f50d48 100644 --- a/projects/app/src/pages/account/model/index.tsx +++ b/projects/app/src/pages/account/model/index.tsx @@ -1,15 +1,72 @@ import { serviceSideProps } from '@fastgpt/web/common/system/nextjs'; -import React from 'react'; +import React, { useState } from 'react'; import AccountContainer from '../components/AccountContainer'; -import { Box } from '@chakra-ui/react'; +import { Box, Button, Flex, useDisclosure } from '@chakra-ui/react'; import ModelTable from '@/components/core/ai/ModelTable'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs'; +import { useTranslation } from 'next-i18next'; +import MyMenu from '@fastgpt/web/components/common/MyMenu'; +import dynamic from 'next/dynamic'; + +const DefaultModal = dynamic(() => import('./components/DefaultModal'), { + ssr: false +}); const ModelProvider = () => { + const { t } = useTranslation(); + const { userInfo } = useUserStore(); + const isRoot = userInfo?.username === 'root'; + + const [tab, setTab] = useState<'model' | 'channel'>('model'); + + const { isOpen: isOpenDefault, onOpen: onOpenDefault, onClose: onCloseDefault } = useDisclosure(); + return ( - - - + + {/* Header */} + {/* + + list={[ + { label: t('account:active_model'), value: 'model' }, + { label: t('account:channel'), value: 'channel' } + ]} + value={tab} + px={8} + py={1} + onChange={setTab} + /> + + {tab === 'model' && ( + {t('account:create_model')}} + menuList={[ + { + children: [ + { + label: t('account:default_model'), + onClick: onOpenDefault + }, + { + label: t('account:custom_model') + } + ] + } + ]} + /> + )} + {tab === 'channel' && } + */} + + {tab === 'model' && } + {/* {tab === 'channel' && } */} + + + + {isOpenDefault && } ); }; diff --git a/projects/app/src/pages/account/team/components/EditInfoModal.tsx b/projects/app/src/pages/account/team/components/EditInfoModal.tsx index 120d8a24b2d8..d171089eaf3c 100644 --- a/projects/app/src/pages/account/team/components/EditInfoModal.tsx +++ b/projects/app/src/pages/account/team/components/EditInfoModal.tsx @@ -1,10 +1,8 @@ -import React, { useCallback } from 'react'; +import React from 'react'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'next-i18next'; import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; -import { compressImgFileAndUpload } from '@/web/common/file/controller'; import { useToast } from '@fastgpt/web/hooks/useToast'; -import { getErrText } from '@fastgpt/global/common/error/utils'; import { useRequest } from '@fastgpt/web/hooks/useRequest'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { Box, Button, Flex, Input, ModalBody, ModalFooter } from '@chakra-ui/react'; @@ -12,7 +10,6 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import Avatar from '@fastgpt/web/components/common/Avatar'; import { postCreateTeam, putUpdateTeam } from '@/web/support/user/team/api'; import { CreateTeamProps } from '@fastgpt/global/support/user/team/controller.d'; -import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants'; import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants'; export type EditTeamFormDataType = CreateTeamProps & { @@ -41,33 +38,15 @@ function EditModal({ }); const avatar = watch('avatar'); - const { File, onOpen: onOpenSelectFile } = useSelectFile({ + const { + File, + onOpen: onOpenSelectFile, + onSelectImage + } = useSelectFile({ fileType: '.jpg,.png,.svg', multiple: false }); - const onSelectFile = useCallback( - async (e: File[]) => { - const file = e[0]; - if (!file) return; - try { - const src = await compressImgFileAndUpload({ - type: MongoImageTypeEnum.teamAvatar, - file, - maxW: 300, - maxH: 300 - }); - setValue('avatar', src); - } catch (err: any) { - toast({ - title: getErrText(err, t('common:common.Select File Failed')), - status: 'warning' - }); - } - }, - [setValue, t, toast] - ); - const { mutate: onclickCreate, isLoading: creating } = useRequest({ mutationFn: async (data: CreateTeamProps) => { return postCreateTeam(data); @@ -154,7 +133,15 @@ function EditModal({ )} - + + onSelectImage(e, { + maxH: 300, + maxW: 300, + callback: (e) => setValue('avatar', e) + }) + } + /> ); } diff --git a/projects/app/src/pages/account/team/components/GroupManage/GroupInfoModal.tsx b/projects/app/src/pages/account/team/components/GroupManage/GroupInfoModal.tsx index 8959d1420046..c2fb98e065ee 100644 --- a/projects/app/src/pages/account/team/components/GroupManage/GroupInfoModal.tsx +++ b/projects/app/src/pages/account/team/components/GroupManage/GroupInfoModal.tsx @@ -6,9 +6,7 @@ import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import { useTranslation } from 'next-i18next'; import React, { useMemo } from 'react'; import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; -import { compressImgFileAndUpload } from '@/web/common/file/controller'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants'; import { useForm } from 'react-hook-form'; import { useContextSelector } from 'use-context-selector'; import { TeamContext } from '../context'; @@ -23,7 +21,11 @@ export type GroupFormType = { function GroupInfoModal({ onClose, editGroupId }: { onClose: () => void; editGroupId?: string }) { const { refetchGroups, groups, refetchMembers } = useContextSelector(TeamContext, (v) => v); const { t } = useTranslation(); - const { File: AvatarSelect, onOpen: onOpenSelectAvatar } = useSelectFile({ + const { + File: AvatarSelect, + onOpen: onOpenSelectAvatar, + onSelectImage + } = useSelectFile({ fileType: '.jpg, .jpeg, .png', multiple: false }); @@ -41,13 +43,10 @@ function GroupInfoModal({ onClose, editGroupId }: { onClose: () => void; editGro const { loading: uploadingAvatar, run: onSelectAvatar } = useRequest2( async (file: File[]) => { - const src = await compressImgFileAndUpload({ - type: MongoImageTypeEnum.groupAvatar, - file: file[0], + return onSelectImage(file, { maxW: 300, maxH: 300 }); - return src; }, { onSuccess: (src: string) => { diff --git a/projects/app/src/pages/account/team/components/GroupManage/index.tsx b/projects/app/src/pages/account/team/components/GroupManage/index.tsx index 895d9112c70a..ba25e90b673f 100644 --- a/projects/app/src/pages/account/team/components/GroupManage/index.tsx +++ b/projects/app/src/pages/account/team/components/GroupManage/index.tsx @@ -1,6 +1,8 @@ import AvatarGroup from '@fastgpt/web/components/common/Avatar/AvatarGroup'; import { Box, + Button, + Flex, HStack, Table, TableContainer, @@ -26,19 +28,35 @@ import MemberTag from '../../../../../components/support/user/team/Info/MemberTa import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import dynamic from 'next/dynamic'; import { useState } from 'react'; +import IconButton from '../OrgManage/IconButton'; const ChangeOwnerModal = dynamic(() => import('./GroupTransferOwnerModal')); +const GroupInfoModal = dynamic(() => import('./GroupInfoModal')); +const ManageGroupMemberModal = dynamic(() => import('./GroupManageMember')); -function MemberTable({ - onEditGroup, - onManageMember -}: { - onEditGroup: (groupId: string) => void; - onManageMember: (groupId: string) => void; -}) { +function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { const { t } = useTranslation(); const { userInfo } = useUserStore(); + const [editGroupId, setEditGroupId] = useState(); + const { + isOpen: isOpenGroupInfo, + onOpen: onOpenGroupInfo, + onClose: onCloseGroupInfo + } = useDisclosure(); + const { + isOpen: isOpenManageGroupMember, + onOpen: onOpenManageGroupMember, + onClose: onCloseManageGroupMember + } = useDisclosure(); + const onEditGroup = (groupId: string) => { + setEditGroupId(groupId); + onOpenGroupInfo(); + }; + const onManageMember = (groupId: string) => { + setEditGroupId(groupId); + onOpenManageGroupMember(); + }; const { ConfirmModal: ConfirmDeleteGroupModal, openConfirm: openDeleteGroupModal } = useConfirm({ type: 'delete', @@ -72,145 +90,184 @@ function MemberTable({ onOpen: onOpenChangeOwner, onClose: onCloseChangeOwner } = useDisclosure(); - const onChangeOwner = (groupId: string) => { setEditGroupId(groupId); onOpenChangeOwner(); }; return ( - - - - - - - - - - - - - {groups?.map((group) => ( - - + + ))} + +
- {t('account_team:group_name')} - {t('account_team:owner')}{t('account_team:member')} - {t('common:common.Action')} -
- + <> + + {Tabs} + {userInfo?.team.permission.hasManagePer && ( + + )} + + + + + + + + + + + + + + + {groups?.map((group) => ( + + + - - - + + - - ))} - -
+ {t('account_team:group_name')} + {t('account_team:owner')}{t('account_team:member')} + {t('common:common.Action')} +
+ + + + ({group.name === DefaultGroupName ? members.length : group.members.length}) + + + item.role === 'owner')?.memberName ?? '' + : members.find( + (item) => + item.tmbId === + group.members.find((item) => item.role === 'owner')?.tmbId + )?.memberName ?? '' + } + avatar={ + group.name === DefaultGroupName + ? members.find((item) => item.role === 'owner')?.avatar ?? '' + : members.find( + (i) => + i.tmbId === + group.members.find((item) => item.role === 'owner')?.tmbId + )?.avatar ?? '' } - avatar={group.avatar} - /> - - ({group.name === DefaultGroupName ? members.length : group.members.length}) - - - - item.role === 'owner')?.memberName ?? '' - : members.find( - (item) => - item.tmbId === - group.members.find((item) => item.role === 'owner')?.tmbId - )?.memberName ?? '' - } - avatar={ - group.name === DefaultGroupName - ? members.find((item) => item.role === 'owner')?.avatar ?? '' - : members.find( - (i) => - i.tmbId === group.members.find((item) => item.role === 'owner')?.tmbId - )?.avatar ?? '' - } - /> - - {group.name === DefaultGroupName ? ( - v.avatar)} groupId={group._id} /> - ) : hasGroupManagePer(group) ? ( - - onManageMember(group._id)}> - members.find((m) => m.tmbId === v.tmbId)?.avatar ?? '' - )} - groupId={group._id} - /> - - - ) : ( - members.find((m) => m.tmbId === v.tmbId)?.avatar ?? '' - )} - groupId={group._id} /> - )} - - {hasGroupManagePer(group) && group.name !== DefaultGroupName && ( - } - menuList={[ - { - children: [ - { - label: t('account_team:edit_info'), - icon: 'edit', - onClick: () => { - onEditGroup(group._id); - } - }, - { - label: t('account_team:manage_member'), - icon: 'support/team/group', - onClick: () => { - onManageMember(group._id); - } - }, - ...(isGroupOwner(group) - ? [ - { - label: t('account_team:transfer_ownership'), - icon: 'modal/changePer', - onClick: () => { - onChangeOwner(group._id); - }, - type: 'primary' as MenuItemType - }, - { - label: t('common:common.Delete'), - icon: 'delete', - onClick: () => { - openDeleteGroupModal(() => delDeleteGroup(group._id))(); + + {group.name === DefaultGroupName ? ( + v.avatar)} groupId={group._id} /> + ) : hasGroupManagePer(group) ? ( + + onManageMember(group._id)}> + members.find((m) => m.tmbId === v.tmbId)?.avatar ?? '' + )} + groupId={group._id} + /> + + + ) : ( + members.find((m) => m.tmbId === v.tmbId)?.avatar ?? '' + )} + groupId={group._id} + /> + )} + + {hasGroupManagePer(group) && group.name !== DefaultGroupName && ( + } + menuList={[ + { + children: [ + { + label: t('account_team:edit_info'), + icon: 'edit', + onClick: () => { + onEditGroup(group._id); + } + }, + { + label: t('account_team:manage_member'), + icon: 'support/team/group', + onClick: () => { + onManageMember(group._id); + } + }, + ...(isGroupOwner(group) + ? [ + { + label: t('account_team:transfer_ownership'), + icon: 'modal/changePer', + onClick: () => { + onChangeOwner(group._id); + }, + type: 'primary' as MenuItemType }, - type: 'danger' as MenuItemType - } - ] - : []) - ] - } - ]} - /> - )} -
-
+ { + label: t('common:common.Delete'), + icon: 'delete', + onClick: () => { + openDeleteGroupModal(() => delDeleteGroup(group._id))(); + }, + type: 'danger' as MenuItemType + } + ] + : []) + ] + } + ]} + /> + )} +
+
+
+ {isOpenChangeOwner && editGroupId && ( )} - + {isOpenGroupInfo && ( + { + onCloseGroupInfo(); + setEditGroupId(undefined); + }} + editGroupId={editGroupId} + /> + )} + {isOpenManageGroupMember && ( + { + onCloseManageGroupMember(); + setEditGroupId(undefined); + }} + editGroupId={editGroupId} + /> + )} + ); } diff --git a/projects/app/src/pages/account/team/components/MemberTable.tsx b/projects/app/src/pages/account/team/components/MemberTable.tsx index b42dc837dcfc..08b9af2bcfea 100644 --- a/projects/app/src/pages/account/team/components/MemberTable.tsx +++ b/projects/app/src/pages/account/team/components/MemberTable.tsx @@ -1,106 +1,224 @@ import Avatar from '@fastgpt/web/components/common/Avatar'; -import { Box, HStack, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'; +import { + Box, + Button, + Flex, + HStack, + Table, + TableContainer, + Tbody, + Td, + Th, + Thead, + Tr, + useDisclosure +} from '@chakra-ui/react'; import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; import { useTranslation } from 'next-i18next'; import { useUserStore } from '@/web/support/user/useUserStore'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; -import MyBox from '@fastgpt/web/components/common/MyBox'; import { delRemoveMember } from '@/web/support/user/team/api'; import Tag from '@fastgpt/web/components/common/Tag'; import Icon from '@fastgpt/web/components/common/Icon'; import GroupTags from '@/components/support/permission/Group/GroupTags'; import { useContextSelector } from 'use-context-selector'; import { TeamContext } from './context'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import dynamic from 'next/dynamic'; +import { useToast } from '@fastgpt/web/hooks/useToast'; +import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { delLeaveTeam } from '@/web/support/user/team/api'; -function MemberTable() { - const { userInfo } = useUserStore(); +const InviteModal = dynamic(() => import('./InviteModal')); +const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal')); + +function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { const { t } = useTranslation(); + const { toast } = useToast(); + const { userInfo, teamPlanStatus } = useUserStore(); + const { feConfigs, setNotSufficientModalType } = useSystemStore(); + + const { groups, refetchGroups, myTeams, refetchTeams, members, refetchMembers, onSwitchTeam } = + useContextSelector(TeamContext, (v) => v); + + const { + isOpen: isOpenTeamTagsAsync, + onOpen: onOpenTeamTagsAsync, + onClose: onCloseTeamTagsAsync + } = useDisclosure(); + const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure(); const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm({ type: 'delete' }); - const { members, groups, refetchMembers, refetchGroups } = useContextSelector( - TeamContext, - (v) => v + const { runAsync: onLeaveTeam } = useRequest2( + async () => { + const defaultTeam = myTeams.find((item) => item.defaultTeam) || myTeams[0]; + // change to personal team + onSwitchTeam(defaultTeam.teamId); + return delLeaveTeam(); + }, + { + onSuccess() { + refetchTeams(); + }, + errorToast: t('account_team:user_team_leave_team_failed') + } ); + const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({ + content: t('account_team:confirm_leave_team') + }); return ( - - - - - - - - - - - - {members?.map((item) => ( - - - - + <> + + {Tabs} + + {userInfo?.team.permission.hasManagePer && feConfigs?.show_team_chat && ( + + )} + {userInfo?.team.permission.hasManagePer && ( + + )} + {!userInfo?.team.permission.isOwner && ( + + )} + + + + + +
- {t('account_team:user_name')} - {t('account_team:member_group')} - {t('common:common.Action')} -
- - - - {item.memberName} - {item.status === 'waiting' && ( - - {t('account_team:waiting')} - - )} - - - - group.members.map((m) => m.tmbId).includes(item.tmbId)) - .map((g) => g.name)} - max={3} - /> - - {userInfo?.team.permission.hasManagePer && - item.role !== TeamMemberRoleEnum.owner && - item.tmbId !== userInfo?.team.tmbId && ( - { - openRemoveMember( - () => - delRemoveMember(item.tmbId).then(() => - Promise.all([refetchGroups(), refetchMembers()]) - ), - undefined, - t('account_team:remove_tip', { - username: item.memberName - }) - )(); - }} - /> - )} -
+ + + + + - ))} - -
+ {t('account_team:user_name')} + {t('account_team:member_group')} + {t('common:common.Action')} +
+ + + {members?.map((item) => ( + + + + + + {item.memberName} + {item.status === 'waiting' && ( + + {t('account_team:waiting')} + + )} + + + + + group.members.map((m) => m.tmbId).includes(item.tmbId)) + .map((g) => g.name)} + max={3} + /> + + + {userInfo?.team.permission.hasManagePer && + item.role !== TeamMemberRoleEnum.owner && + item.tmbId !== userInfo?.team.tmbId && ( + { + openRemoveMember( + () => + delRemoveMember(item.tmbId).then(() => + Promise.all([refetchGroups(), refetchMembers()]) + ), + undefined, + t('account_team:remove_tip', { + username: item.memberName + }) + )(); + }} + /> + )} + + + ))} + + + + +
+ - - -
+ + {isOpenInvite && userInfo?.team?.teamId && ( + + )} + {isOpenTeamTagsAsync && } + ); } diff --git a/projects/app/src/pages/account/team/components/OrgManage/IconButton.tsx b/projects/app/src/pages/account/team/components/OrgManage/IconButton.tsx new file mode 100644 index 000000000000..cb543dc1ab36 --- /dev/null +++ b/projects/app/src/pages/account/team/components/OrgManage/IconButton.tsx @@ -0,0 +1,31 @@ +import { IconProps } from '@chakra-ui/react'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import type { IconNameType } from '@fastgpt/web/components/common/Icon/type'; + +function IconButton({ + name, + w = '1rem', + h = '1rem', + ...props +}: { + name: IconNameType; +} & IconProps) { + return ( + + ); +} + +export default IconButton; diff --git a/projects/app/src/pages/account/team/components/OrgManage/OrgInfoModal.tsx b/projects/app/src/pages/account/team/components/OrgManage/OrgInfoModal.tsx new file mode 100644 index 000000000000..c2e49b368206 --- /dev/null +++ b/projects/app/src/pages/account/team/components/OrgManage/OrgInfoModal.tsx @@ -0,0 +1,162 @@ +import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; +import { postCreateOrg, putUpdateOrg } from '@/web/support/user/team/org/api'; +import { Button, HStack, Input, ModalBody, ModalFooter, Textarea } from '@chakra-ui/react'; +import { DEFAULT_ORG_AVATAR } from '@fastgpt/global/common/system/constants'; +import Avatar from '@fastgpt/web/components/common/Avatar'; +import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; +import MyModal from '@fastgpt/web/components/common/MyModal'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { useTranslation } from 'next-i18next'; +import dynamic from 'next/dynamic'; +import { useForm } from 'react-hook-form'; + +export type OrgFormType = { + _id: string; + avatar: string; + description?: string; + name: string; + path: string; + parentId?: string; +}; + +export const defaultOrgForm: OrgFormType = { + _id: '', + avatar: '', + description: '', + name: '', + path: '' +}; + +function OrgInfoModal({ + editOrg, + onClose, + onSuccess +}: { + editOrg: OrgFormType; + onClose: () => void; + onSuccess: () => void; +}) { + const { t } = useTranslation(); + + const isEdit = !!editOrg._id; + + const { register, handleSubmit, setValue, watch } = useForm({ + defaultValues: { + name: editOrg.name, + avatar: editOrg.avatar, + description: editOrg.description + } + }); + const avatar = watch('avatar'); + + const { run: onCreate, loading: isLoadingCreate } = useRequest2( + async (data: OrgFormType) => { + if (!editOrg.parentId) return; + return postCreateOrg({ + name: data.name, + avatar: data.avatar, + parentId: editOrg.parentId, + description: data.description + }); + }, + { + successToast: t('common:common.Create Success'), + onSuccess: () => { + onClose(); + onSuccess(); + } + } + ); + + const { run: onUpdate, loading: isLoadingUpdate } = useRequest2( + async (data: OrgFormType) => { + if (!editOrg._id) return; + return putUpdateOrg({ + orgId: editOrg._id, + name: data.name, + avatar: data.avatar, + description: data.description + }); + }, + { + successToast: t('common:common.Update Success'), + onSuccess: () => { + onClose(); + onSuccess(); + } + } + ); + + const { + File: AvatarSelect, + onOpen: onOpenSelectAvatar, + onSelectImage + } = useSelectFile({ + fileType: '.jpg, .jpeg, .png', + multiple: false + }); + const { loading: uploadingAvatar, run: onSelectAvatar } = useRequest2( + async (file: File[]) => { + return onSelectImage(file, { + maxW: 300, + maxH: 300 + }); + }, + { + onSuccess: (src: string) => { + setValue('avatar', src); + } + } + ); + + const isLoading = uploadingAvatar || isLoadingUpdate || isLoadingCreate; + + return ( + + + {t('user:team.avatar_and_name')} + + + + + {t('account_team:org_description')} +