V4.12.4 features (#5626)
* fix: push again, user select option button and form input radio content overflow (#5601) * fix: push again, user select option button and form input radio content overflow * fix: use useCallback instead of useMemo, fix unnecessary delete * fix: Move the variable inside the component * fix: do not pass valueLabel to MySelect * ui * del collection api adapt * refactor: inherit permission (#5529) * refactor: permission update conflict check function * refactor(permission): app collaborator update api * refactor(permission): support app update collaborator * feat: support fe permission conflict check * refactor(permission): app permission * refactor(permission): dataset permission * refactor(permission): team permission * chore: fe adjust * fix: type error * fix: audit pagiation * fix: tc * chore: initv4130 * fix: app/dataset auth logic * chore: move code * refactor(permission): remove selfPermission * fix: mock * fix: test * fix: app & dataset auth * fix: inherit * test(inheritPermission): test syncChildrenPermission * prompt editor add list plugin (#5620) * perf: search result (#5608) * fix: table size (#5598) * temp: list value * backspace * optimize code --------- Co-authored-by: Archer <545436317@qq.com> Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com> * fix: fe & member list (#5619) * chore: initv4130 * fix: MemberItemCard * fix: MemberItemCard * chore: fe adjust & init script * perf: test code * doc * fix debug variables (#5617) * perf: search result (#5608) * fix: table size (#5598) * fix debug variables * fix --------- Co-authored-by: Archer <545436317@qq.com> Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com> * perf: member ui * fix: inherit bug (#5624) * refactor(permission): remove getClbsWithInfo, which is useless * fix: app list privateApp * fix: get infos * perf(fe): remove delete icon when it is disable in MemberItemCard * fix: dataset private dataset * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Archer <545436317@qq.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * perf: auto coupon * chore: upgrade script & get infos avatar (#5625) * fix: get infos * chore: initv4130 * feat: support WecomRobot publish, and fix AesKey can not save bug (#5526) * feat: resolve conflicts * fix: add param 'show_publish_wecom' * feat: abstract out WecomCrypto type * doc: wecom robot document * fix: solve instability in AI output * doc: update some pictures * feat: remove functions from request.ts to chat.ts and toolCall.ts * doc: wecom robot doc update * fix * delete unused code * doc: update version and prompt * feat: remove wecom crypto, delete wecom code in workflow * feat: delete unused codes --------- Co-authored-by: heheer <zhiyu44@qq.com> * remove test * rename init shell * feat: collection page store * reload sandbox * pysandbox * remove log * chore: remove useless code (#5629) * chore: remove useless code * fix: checkConflict * perf: support hidden type for RoleList * fix: copy node * update doc * fix(permission): some bug (#5632) * fix: app/dataset list * fix: inherit bug * perf: del app;i18n;save chat * fix: test * i18n * fix: sumper overflow return OwnerRoleVal (#5633) * remove invalid code * fix: scroll * fix: objectId * update next * update package * object id * mock redis * feat: add redis append to resolve wecom stream response (#5643) * feat: resolve conflicts * fix: add param 'show_publish_wecom' * feat: abstract out WecomCrypto type * doc: wecom robot document * fix: solve instability in AI output * doc: update some pictures * feat: remove functions from request.ts to chat.ts and toolCall.ts * doc: wecom robot doc update * fix * delete unused code * doc: update version and prompt * feat: remove wecom crypto, delete wecom code in workflow * feat: delete unused codes * feat: add redis append method --------- Co-authored-by: heheer <zhiyu44@qq.com> * cache per * fix(test): init team sub when creating mocked user (#5646) * fix: button is not vertically centered (#5647) * doc * fix: gridFs objectId (#5649) --------- Co-authored-by: Zeng Qingwen <143274079+fishwww-ww@users.noreply.github.com> Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Co-authored-by: heheer <heheer@sealos.io> Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: heheer <zhiyu44@qq.com>
|
|
@ -868,8 +868,12 @@ curl --location --request PUT 'http://localhost:3000/api/core/dataset/collection
|
|||
<Tab value="请求示例" >
|
||||
|
||||
```bash
|
||||
curl --location --request DELETE 'http://localhost:3000/api/core/dataset/collection/delete?id=65aa2a64e6cb9b8ccdc00de8' \
|
||||
--header 'Authorization: Bearer {{authorization}}' \
|
||||
curl --location --request POST 'http://localhost:3000/api/core/dataset/collection/delete' \
|
||||
--header 'Authorization: Bearer fastgpt-' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"collectionIds": ["65a8cdcb0d70d3de0bf08d0a"]
|
||||
}'
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
|
@ -877,7 +881,7 @@ curl --location --request DELETE 'http://localhost:3000/api/core/dataset/collect
|
|||
<Tab value="参数说明" >
|
||||
|
||||
<div>
|
||||
- id: 集合的ID
|
||||
- collectionIds: 集合的 ID 列表
|
||||
</div>
|
||||
|
||||
</Tab>
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ description: FastGPT 文档目录
|
|||
- [/docs/upgrading/4-12/4121](/docs/upgrading/4-12/4121)
|
||||
- [/docs/upgrading/4-12/4122](/docs/upgrading/4-12/4122)
|
||||
- [/docs/upgrading/4-12/4123](/docs/upgrading/4-12/4123)
|
||||
- [/docs/upgrading/4-12/4124](/docs/upgrading/4-12/4124)
|
||||
- [/docs/upgrading/4-8/40](/docs/upgrading/4-8/40)
|
||||
- [/docs/upgrading/4-8/41](/docs/upgrading/4-8/41)
|
||||
- [/docs/upgrading/4-8/42](/docs/upgrading/4-8/42)
|
||||
|
|
@ -180,3 +181,4 @@ description: FastGPT 文档目录
|
|||
- [/docs/use-cases/external-integration/feishu](/docs/use-cases/external-integration/feishu)
|
||||
- [/docs/use-cases/external-integration/official_account](/docs/use-cases/external-integration/official_account)
|
||||
- [/docs/use-cases/external-integration/openapi](/docs/use-cases/external-integration/openapi)
|
||||
- [/docs/use-cases/external-integration/wecom](/docs/use-cases/external-integration/wecom)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
title: 'V4.12.4(进行)'
|
||||
description: 'FastGPT V4.12.4 更新说明'
|
||||
---
|
||||
|
||||
## 🚀 新增内容
|
||||
|
||||
1. 商业版支持企微发布渠道。
|
||||
|
||||
## ⚙️ 优化
|
||||
|
||||
1. 权限继承优化,子资源权限高于父级时,不会强制打断继承模式。
|
||||
2. Prompt 编辑器支持列表渲染。
|
||||
3. 数据页返回知识库列表,保持分页。
|
||||
4. 知识库上传文件成功后,返回对应上传目录。
|
||||
5. 删除应用,减少事务操作。
|
||||
6. 用户选择 UI。
|
||||
|
||||
## 🐛 修复
|
||||
|
||||
1. HTTP 工具空指针,导致无法编辑。
|
||||
2. python 代码运行,入参无法是 boolean 值。
|
||||
3. debug 模式下,全局变量未传递。
|
||||
|
||||
## 🔨 插件更新
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "4.12.x",
|
||||
"description": "",
|
||||
"pages": ["4123", "4122", "4121", "4120"]
|
||||
"pages": ["4124", "4123", "4122", "4121", "4120"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"title": "外部调用 FastGPT",
|
||||
"description": "外部应用通过多种方式调用 FastGPT 功能的教程",
|
||||
"pages": ["openapi", "feishu", "dingtalk", "official_account"]
|
||||
"pages": ["openapi", "feishu", "dingtalk", "wecom", "official_account"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
title: 接入企微机器人教程
|
||||
description: FastGPT 接入企微机器人教程
|
||||
---
|
||||
|
||||
从 4.12.4 版本起,FastGPT 商业版支持直接接入企微机器人,无需额外的 API。
|
||||
|
||||
## 1.配置可信域名和可信IP
|
||||
|
||||
点击企微头像,打开管理企业
|
||||
|
||||

|
||||
|
||||
在应用管理中找到"自建"-"创建应用"
|
||||
|
||||

|
||||
|
||||
创建好应用后, 下拉, 依次配置"网页授权及JS-SDK"和"企业可信IP"
|
||||
|
||||

|
||||
|
||||
其中, 网页授权及JS-SDK要求按照企微指引,完成域名归属认证
|
||||
|
||||

|
||||
|
||||
企业可信IP要求为企业服务器IP, 后续企微的回调URL将请求到此IP
|
||||
|
||||

|
||||
|
||||
## 2. 创建企业自建应用
|
||||
|
||||
前往 FastGPT ,选择想要接入的应用,在 发布渠道 页面,新建一个接入企微智能机器人的发布渠道,填写好基础信息。
|
||||
|
||||

|
||||
|
||||
现在回到企业微信平台,找到 Corp ID, Agent ID, Token, AES Key 信息并填写回 FastGPT 平台
|
||||
|
||||

|
||||
|
||||
在"我的企业"里找到企业 ID, 填写到 FastGPT 的 Corp ID 中
|
||||
|
||||

|
||||
|
||||
在应用中找到 Agent Id 和 Secret, 并填写回 FastGPT
|
||||
|
||||

|
||||
|
||||
点击"消息接收"-"设置API接收"
|
||||
|
||||

|
||||
|
||||
随机生成或者手动输入 Token 和 Encoding-Key, 分别填写到 FastGPT 的 Token 和 AES Key 中
|
||||
|
||||

|
||||
|
||||
填写完成后确认创建
|
||||
|
||||
然后点击请求地址, 复制页面中的链接
|
||||
|
||||

|
||||
|
||||
回到刚才的配置详情, 将刚才复制的链接填入 URL 框中, 并点击创建
|
||||
|
||||
注意: 若复制的链接是以 "http://localhost" 开头, 需要将本地地址改为企业主体域名
|
||||
|
||||
因为企微会给填写的 URL 发送验证密文, 若 URL 为本地地址, 则本地接收不到企微的密文
|
||||
|
||||
若 URL 不是企业主体域名, 则验证会失败
|
||||
|
||||
## 3. 创建智能机器人
|
||||
|
||||
在"安全与管理" - "管理工具"页面找到"智能机器人" ( 注意: 只有企业创建者或超级管理员才有权限看到这个入口 )
|
||||
|
||||

|
||||
|
||||
创建机器人页面,下拉,找到,点击"API模式创建"
|
||||
|
||||

|
||||
|
||||
与刚才配置自建应用同理, 配置这三个参数
|
||||
|
||||

|
||||
|
||||
注意: 这里的 Agent ID , 和上面的不同, 可以先随意填写一个值, 后续会根据企业微信提供的数据重新更改
|
||||
|
||||
Secret 为用户自己决定的密令
|
||||
|
||||
填写完成后确认创建
|
||||
|
||||
然后点击请求地址, 复制页面中的链接, 链接的地址也必须为企业主体域名
|
||||
|
||||
创建完成后, 找到智能机器人的配置详情
|
||||
|
||||

|
||||
|
||||
复制 Bot ID, 填写到 FastGPT 的 Agent ID 中
|
||||
|
||||

|
||||
|
||||
## 4. 使用智能机器人
|
||||
|
||||
在企业微信平台的"通讯录",即可找到创建的机器人,就可以发送消息了
|
||||
|
||||

|
||||
|
||||
## FAQ
|
||||
|
||||
### 发送了消息,没响应
|
||||
|
||||
1. 检查企微机器人回调地址、权限等是否正确。
|
||||
2. 查看 FastGPT 对话日志,是否有对应的提问记录
|
||||
3. 如果没记录,则可能是应用运行报错了,可以先试试最简单的机器人。(飞书机器人无法输入全局变量、文件、图片内容)
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
"document/content/docs/introduction/development/modelConfig/ppio.mdx": "2025-08-05T23:20:39+08:00",
|
||||
"document/content/docs/introduction/development/modelConfig/siliconCloud.mdx": "2025-08-05T23:20:39+08:00",
|
||||
"document/content/docs/introduction/development/openapi/chat.mdx": "2025-08-14T18:54:47+08:00",
|
||||
"document/content/docs/introduction/development/openapi/dataset.mdx": "2025-08-14T18:54:47+08:00",
|
||||
"document/content/docs/introduction/development/openapi/dataset.mdx": "2025-09-11T10:29:11+08:00",
|
||||
"document/content/docs/introduction/development/openapi/intro.mdx": "2025-08-14T18:54:47+08:00",
|
||||
"document/content/docs/introduction/development/openapi/share.mdx": "2025-08-05T23:20:39+08:00",
|
||||
"document/content/docs/introduction/development/proxy/cloudflare.mdx": "2025-07-23T21:35:03+08:00",
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
"document/content/docs/introduction/development/sealos.mdx": "2025-08-05T23:20:39+08:00",
|
||||
"document/content/docs/introduction/guide/DialogBoxes/htmlRendering.mdx": "2025-07-23T21:35:03+08:00",
|
||||
"document/content/docs/introduction/guide/DialogBoxes/quoteList.mdx": "2025-07-23T21:35:03+08:00",
|
||||
"document/content/docs/introduction/guide/admin/sso.mdx": "2025-07-24T13:00:27+08:00",
|
||||
"document/content/docs/introduction/guide/admin/sso.mdx": "2025-09-08T20:07:04+08:00",
|
||||
"document/content/docs/introduction/guide/admin/teamMode.mdx": "2025-08-27T16:59:57+08:00",
|
||||
"document/content/docs/introduction/guide/course/ai_settings.mdx": "2025-07-24T13:00:27+08:00",
|
||||
"document/content/docs/introduction/guide/course/chat_input_guide.mdx": "2025-07-23T21:35:03+08:00",
|
||||
|
|
@ -97,15 +97,16 @@
|
|||
"document/content/docs/protocol/terms.en.mdx": "2025-08-03T22:37:45+08:00",
|
||||
"document/content/docs/protocol/terms.mdx": "2025-08-03T22:37:45+08:00",
|
||||
"document/content/docs/toc.en.mdx": "2025-08-04T13:42:36+08:00",
|
||||
"document/content/docs/toc.mdx": "2025-08-29T01:24:19+08:00",
|
||||
"document/content/docs/toc.mdx": "2025-09-12T12:58:39+08:00",
|
||||
"document/content/docs/upgrading/4-10/4100.mdx": "2025-08-02T19:38:37+08:00",
|
||||
"document/content/docs/upgrading/4-10/4101.mdx": "2025-08-02T19:38:37+08:00",
|
||||
"document/content/docs/upgrading/4-10/4101.mdx": "2025-09-08T20:07:20+08:00",
|
||||
"document/content/docs/upgrading/4-11/4110.mdx": "2025-08-05T23:20:39+08:00",
|
||||
"document/content/docs/upgrading/4-11/4111.mdx": "2025-08-07T22:49:09+08:00",
|
||||
"document/content/docs/upgrading/4-12/4120.mdx": "2025-09-07T14:41:48+08:00",
|
||||
"document/content/docs/upgrading/4-12/4121.mdx": "2025-09-07T14:41:48+08:00",
|
||||
"document/content/docs/upgrading/4-12/4122.mdx": "2025-09-07T14:41:48+08:00",
|
||||
"document/content/docs/upgrading/4-12/4123.mdx": "2025-09-07T14:41:48+08:00",
|
||||
"document/content/docs/upgrading/4-12/4123.mdx": "2025-09-07T20:55:14+08:00",
|
||||
"document/content/docs/upgrading/4-12/4124.mdx": "2025-09-13T01:34:04+08:00",
|
||||
"document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00",
|
||||
"document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00",
|
||||
"document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00",
|
||||
|
|
@ -185,5 +186,6 @@
|
|||
"document/content/docs/use-cases/external-integration/feishu.mdx": "2025-07-24T14:23:04+08:00",
|
||||
"document/content/docs/use-cases/external-integration/official_account.mdx": "2025-08-05T23:20:39+08:00",
|
||||
"document/content/docs/use-cases/external-integration/openapi.mdx": "2025-08-14T18:54:47+08:00",
|
||||
"document/content/docs/use-cases/external-integration/wecom.mdx": "2025-09-12T12:58:39+08:00",
|
||||
"document/content/docs/use-cases/index.mdx": "2025-07-24T14:23:04+08:00"
|
||||
}
|
||||
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 304 KiB |
|
After Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 444 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 341 KiB |
|
After Width: | Height: | Size: 281 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 305 KiB |
|
|
@ -20,7 +20,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@chakra-ui/cli": "^2.4.1",
|
||||
"typescript": "^5.1.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vitest/coverage-v8": "^3.0.9",
|
||||
|
|
@ -28,13 +27,14 @@
|
|||
"eslint-config-next": "^14.1.0",
|
||||
"husky": "^8.0.3",
|
||||
"i18next": "23.16.8",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lint-staged": "^13.3.0",
|
||||
"mongodb-memory-server": "^10.1.4",
|
||||
"next-i18next": "15.4.2",
|
||||
"prettier": "3.2.4",
|
||||
"react-i18next": "14.1.2",
|
||||
"typescript": "^5.1.3",
|
||||
"vitest": "^3.0.9",
|
||||
"js-yaml": "^4.1.0",
|
||||
"mongodb-memory-server": "^10.1.4",
|
||||
"zhlint": "^0.7.4"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export type FastGPTFeConfigsType = {
|
|||
show_dataset_yuque?: boolean;
|
||||
show_publish_feishu?: boolean;
|
||||
show_publish_dingtalk?: boolean;
|
||||
show_publish_wecom?: boolean;
|
||||
show_publish_offiaccount?: boolean;
|
||||
|
||||
show_dataset_enhance?: boolean;
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ export const valueTypeFormat = (value: any, valueType?: WorkflowIOValueTypeEnum)
|
|||
return typeof value === 'object' ? JSON.stringify(value) : String(value);
|
||||
}
|
||||
if (valueType === WorkflowIOValueTypeEnum.number) {
|
||||
if (value === '') return undefined;
|
||||
if (value === '') return null;
|
||||
return Number(value);
|
||||
}
|
||||
if (valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"@fastgpt-sdk/plugin": "^0.1.16",
|
||||
"@apidevtools/swagger-parser": "^10.1.0",
|
||||
"@bany/curl-to-json": "^1.2.8",
|
||||
"axios": "^1.8.2",
|
||||
"axios": "^1.12.1",
|
||||
"cron-parser": "^4.9.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"encoding": "^0.1.13",
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
"jschardet": "3.1.1",
|
||||
"json5": "^2.2.3",
|
||||
"nanoid": "^5.1.3",
|
||||
"next": "14.2.28",
|
||||
"next": "14.2.32",
|
||||
"openai": "4.61.0",
|
||||
"openapi-types": "^12.1.3",
|
||||
"timezones-list": "^3.0.2",
|
||||
|
|
|
|||
|
|
@ -1,31 +1,33 @@
|
|||
import type { UpdateAppCollaboratorBody } from 'core/app/collaborator';
|
||||
import type { RequireOnlyOne } from '../../common/type/utils';
|
||||
import { RequireAtLeastOne } from '../../common/type/utils';
|
||||
import type { Permission } from './controller';
|
||||
import type { PermissionValueType } from './type';
|
||||
import type { PermissionValueType, RoleValueType } from './type';
|
||||
|
||||
export type CollaboratorItemType = {
|
||||
teamId: string;
|
||||
permission: Permission;
|
||||
name: string;
|
||||
avatar: string;
|
||||
} & RequireOnlyOne<{
|
||||
export type CollaboratorIdType = RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
export type UpdateClbPermissionProps<addOnly = false> = {
|
||||
members?: string[];
|
||||
groups?: string[];
|
||||
orgs?: string[];
|
||||
} & (addOnly extends true
|
||||
? {}
|
||||
: {
|
||||
permission: PermissionValueType;
|
||||
});
|
||||
export type CollaboratorItemDetailType = {
|
||||
teamId: string;
|
||||
permission: Permission;
|
||||
name: string;
|
||||
avatar: string;
|
||||
} & CollaboratorIdType;
|
||||
|
||||
export type DeletePermissionQuery = RequireOnlyOne<{
|
||||
tmbId?: string;
|
||||
groupId?: string;
|
||||
orgId?: string;
|
||||
}>;
|
||||
export type CollaboratorItemType = {
|
||||
permission: PermissionValueType;
|
||||
} & CollaboratorIdType;
|
||||
|
||||
export type UpdateClbPermissionProps = {
|
||||
collaborators: CollaboratorItemType[];
|
||||
};
|
||||
|
||||
export type DeletePermissionQuery = CollaboratorIdType;
|
||||
|
||||
export type CollaboratorListType = {
|
||||
clbs: CollaboratorItemDetailType[];
|
||||
parentClbs?: CollaboratorItemDetailType[];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import type { UserModelSchema } from '../user/type';
|
||||
import type { RequireOnlyOne } from '../../common/type/utils';
|
||||
import type { TeamMemberSchema } from '../user/team/type';
|
||||
import { MemberGroupSchemaType } from './memberGroup/type';
|
||||
import type { TeamMemberWithUserSchema } from '../user/team/type';
|
||||
import type { CommonPerKeyEnum, CommonRoleKeyEnum } from './constant';
|
||||
import { AuthUserTypeEnum, type CommonPerKeyEnum, type PerResourceTypeEnum } from './constant';
|
||||
import type { CommonRoleKeyEnum } from './constant';
|
||||
import { type CommonPerKeyEnum, 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.
|
||||
|
|
@ -18,14 +16,14 @@ export type ResourceType = `${PerResourceTypeEnum}`;
|
|||
/**
|
||||
* Define the roles. Each role is a binary number, only one bit is set to 1.
|
||||
*/
|
||||
export type RoleListType<T = {}> = Readonly<
|
||||
export type RoleListType<T extends string | number | symbol = CommonRoleKeyEnum> = Readonly<
|
||||
Record<
|
||||
T | CommonRoleKeyEnum,
|
||||
Readonly<{
|
||||
name: string;
|
||||
description: string;
|
||||
value: RoleValueType;
|
||||
checkBoxType: 'single' | 'multiple';
|
||||
checkBoxType: 'single' | 'multiple' | 'hidden';
|
||||
}>
|
||||
>
|
||||
>;
|
||||
|
|
@ -43,7 +41,7 @@ export type RoleListType<T = {}> = Readonly<
|
|||
* write: 0b110, // bad, should be 0b010
|
||||
* }
|
||||
*/
|
||||
export type PermissionListType<T = {}> = Readonly<
|
||||
export type PermissionListType<T extends string | number | symbol = CommonPerKeyEnum> = Readonly<
|
||||
Record<T | CommonPerKeyEnum, PermissionValueType>
|
||||
>;
|
||||
|
||||
|
|
@ -31,11 +31,13 @@ export const TeamPerList: PermissionListType<TeamPerKeyEnum> = {
|
|||
export const TeamRoleList: RoleListType<TeamRoleKeyEnum> = {
|
||||
[CommonPerKeyEnum.read]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.read],
|
||||
name: i18nT('common:permission.common_member'),
|
||||
value: 0b000100
|
||||
},
|
||||
[CommonPerKeyEnum.write]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.write],
|
||||
value: 0b000010
|
||||
value: 0b000010,
|
||||
checkBoxType: 'hidden'
|
||||
},
|
||||
[CommonPerKeyEnum.manage]: {
|
||||
...CommonRoleList[CommonPerKeyEnum.manage],
|
||||
|
|
|
|||
|
|
@ -1,52 +1,182 @@
|
|||
import type { CollaboratorIdType, CollaboratorItemType } from './collaborator';
|
||||
import { ManageRoleVal, OwnerRoleVal } from './constant';
|
||||
import type { RoleValueType } from './type';
|
||||
import { type PermissionValueType } from './type';
|
||||
import { NullRoleVal, PermissionTypeEnum } from './constant';
|
||||
import type { Permission } from './controller';
|
||||
|
||||
/* team public source, or owner source in team */
|
||||
export function mongoRPermission({
|
||||
teamId,
|
||||
tmbId,
|
||||
permission
|
||||
}: {
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
permission: Permission;
|
||||
}) {
|
||||
if (permission.isOwner) {
|
||||
return {
|
||||
teamId
|
||||
};
|
||||
}
|
||||
return {
|
||||
teamId,
|
||||
$or: [{ permission: PermissionTypeEnum.public }, { tmbId }]
|
||||
};
|
||||
}
|
||||
export function mongoOwnerPermission({ teamId, tmbId }: { teamId: string; tmbId: string }) {
|
||||
return {
|
||||
teamId,
|
||||
tmbId
|
||||
};
|
||||
}
|
||||
|
||||
// return permission-related schema to define the schema of resources
|
||||
export function getPermissionSchema(defaultPermission: PermissionValueType = NullRoleVal) {
|
||||
return {
|
||||
defaultPermission: {
|
||||
type: Number,
|
||||
default: defaultPermission
|
||||
},
|
||||
inheritPermission: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum the permission value.
|
||||
* If no permission value is provided, return undefined to fallback to default value.
|
||||
* @param per permission value (number)
|
||||
* @returns sum of permission value
|
||||
*/
|
||||
export const sumPer = (...per: PermissionValueType[]) => {
|
||||
if (per.length === 0) {
|
||||
// prevent sum 0 value, to fallback to default value
|
||||
return undefined;
|
||||
}
|
||||
return per.reduce((acc, cur) => acc | cur, 0);
|
||||
const res = per.reduce((acc, cur) => acc | cur, 0);
|
||||
if (res < 0) {
|
||||
// overflowed
|
||||
return OwnerRoleVal;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the update cause conflict (need to remove inheritance permission).
|
||||
* Conflict condition:
|
||||
* The updated collaborator is a parent collaborator.
|
||||
* @param parentClbs parent collaborators
|
||||
* @param oldChildClbs old child collaborators
|
||||
* @param newChildClbs new child collaborators
|
||||
*/
|
||||
export const checkRoleUpdateConflict = ({
|
||||
parentClbs,
|
||||
newChildClbs
|
||||
}: {
|
||||
parentClbs: CollaboratorItemType[];
|
||||
newChildClbs: CollaboratorItemType[];
|
||||
}): boolean => {
|
||||
if (parentClbs.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use a Map for faster lookup by teamId
|
||||
const parentClbRoleMap = new Map(
|
||||
parentClbs.map((clb) => [
|
||||
getCollaboratorId(clb),
|
||||
{
|
||||
...clb
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
const changedClbs = getChangedCollaborators({
|
||||
newRealClbs: newChildClbs,
|
||||
oldRealClbs: parentClbs
|
||||
});
|
||||
|
||||
for (const changedClb of changedClbs) {
|
||||
const parent = parentClbRoleMap.get(getCollaboratorId(changedClb));
|
||||
if (parent && ((changedClb.changedRole & parent.permission) !== 0 || changedClb.deleted)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export type ChangedClbType = {
|
||||
changedRole: RoleValueType;
|
||||
deleted: boolean;
|
||||
} & CollaboratorIdType;
|
||||
|
||||
/**
|
||||
* Get changed collaborators.
|
||||
* return empty array if all collaborators are unchanged.
|
||||
*
|
||||
* for each return item:
|
||||
* ```typescript
|
||||
* {
|
||||
* // ... ids
|
||||
* changedRole: number; // set bit means the role is changed
|
||||
* deleted: boolean; // is deleted
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* **special**: for low 3 bit: always get the lowest change, unset the higher change.
|
||||
*/
|
||||
export const getChangedCollaborators = ({
|
||||
oldRealClbs,
|
||||
newRealClbs
|
||||
}: {
|
||||
oldRealClbs: CollaboratorItemType[];
|
||||
newRealClbs: CollaboratorItemType[];
|
||||
}): ChangedClbType[] => {
|
||||
if (oldRealClbs.length === 0) {
|
||||
return newRealClbs.map((clb) => ({
|
||||
...clb,
|
||||
changedRole: clb.permission,
|
||||
deleted: false
|
||||
}));
|
||||
}
|
||||
const oldClbsMap = new Map(oldRealClbs.map((clb) => [getCollaboratorId(clb), clb]));
|
||||
const changedClbs: ChangedClbType[] = [];
|
||||
for (const newClb of newRealClbs) {
|
||||
const oldClb = oldClbsMap.get(getCollaboratorId(newClb));
|
||||
if (!oldClb) {
|
||||
changedClbs.push({
|
||||
...newClb,
|
||||
changedRole: newClb.permission,
|
||||
deleted: false
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const changedRole = oldClb.permission ^ newClb.permission;
|
||||
if (changedRole) {
|
||||
changedClbs.push({
|
||||
...newClb,
|
||||
changedRole,
|
||||
deleted: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const oldClb of oldRealClbs) {
|
||||
const newClb = newRealClbs.find((clb) => getCollaboratorId(clb) === getCollaboratorId(oldClb));
|
||||
if (!newClb) {
|
||||
changedClbs.push({
|
||||
...oldClb,
|
||||
changedRole: oldClb.permission,
|
||||
deleted: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
changedClbs.forEach((clb) => {
|
||||
// For the lowest 3 bits, only keep the lowest set bit as 1, clear other lower bits, keep higher bits unchanged
|
||||
const low3 = clb.changedRole & 0b111;
|
||||
const lowestBit = low3 & -low3;
|
||||
clb.changedRole = (clb.changedRole & ~0b111) | lowestBit;
|
||||
});
|
||||
|
||||
return changedClbs;
|
||||
};
|
||||
|
||||
export const getCollaboratorId = (clb: CollaboratorIdType) =>
|
||||
(clb.tmbId || clb.groupId || clb.orgId)!;
|
||||
|
||||
export const mergeCollaboratorList = <T extends CollaboratorItemType>({
|
||||
parentClbs,
|
||||
childClbs
|
||||
}: {
|
||||
parentClbs: T[];
|
||||
childClbs: T[];
|
||||
}) => {
|
||||
const idToClb = new Map<string, T>();
|
||||
|
||||
// Add all items from list1
|
||||
for (const parentClb of parentClbs) {
|
||||
if (parentClb.permission === OwnerRoleVal) {
|
||||
idToClb.set(getCollaboratorId(parentClb), { ...parentClb, permission: ManageRoleVal });
|
||||
continue;
|
||||
}
|
||||
idToClb.set(getCollaboratorId(parentClb), { ...parentClb });
|
||||
}
|
||||
|
||||
// Merge permissions from list2
|
||||
for (const childClb of childClbs) {
|
||||
const id = getCollaboratorId(childClb);
|
||||
if (idToClb.has(id)) {
|
||||
// If already exists, merge permission bits
|
||||
const original = idToClb.get(id)!;
|
||||
idToClb.set(id, {
|
||||
...original,
|
||||
permission: sumPer(original.permission, childClb.permission)!
|
||||
});
|
||||
} else {
|
||||
idToClb.set(id, { ...childClb });
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(idToClb.values());
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { StandardSubLevelEnum, SubModeEnum } from '../sub/constants';
|
||||
import type { BillTypeEnum } from './constants';
|
||||
import type { BillTypeEnum, BillPayWayEnum } from './constants';
|
||||
import { DrawBillQRItem } from './constants';
|
||||
|
||||
export type CreateOrderResponse = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { retryFn } from '@fastgpt/global/common/system/utils';
|
||||
import { connectionMongo } from '../../mongo';
|
||||
import { connectionMongo, Types } from '../../mongo';
|
||||
import { MongoRawTextBufferSchema, bucketName } from './schema';
|
||||
import { addLog } from '../../system/log';
|
||||
import { setCron } from '../../system/cron';
|
||||
|
|
@ -86,7 +86,7 @@ export const getRawTextBuffer = async (sourceId: string) => {
|
|||
}
|
||||
|
||||
// Read file content
|
||||
const downloadStream = gridBucket.openDownloadStream(bufferData._id);
|
||||
const downloadStream = gridBucket.openDownloadStream(new Types.ObjectId(bufferData._id));
|
||||
|
||||
const fileBuffers = await gridFsStream2Buffer(downloadStream);
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ export const deleteRawTextBuffer = async (sourceId: string): Promise<boolean> =>
|
|||
return false;
|
||||
}
|
||||
|
||||
await gridBucket.delete(buffer._id);
|
||||
await gridBucket.delete(new Types.ObjectId(buffer._id));
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
|
@ -155,7 +155,7 @@ export const clearExpiredRawTextBufferCron = async () => {
|
|||
|
||||
for (const item of data) {
|
||||
try {
|
||||
await gridBucket.delete(item._id);
|
||||
await gridBucket.delete(new Types.ObjectId(item._id));
|
||||
} catch (error) {
|
||||
addLog.error('Delete expired raw text buffer error', error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,33 @@ const addCommonMiddleware = (schema: mongoose.Schema) => {
|
|||
}
|
||||
next();
|
||||
});
|
||||
|
||||
// Convert _id to string
|
||||
schema.post(/^find/, function (docs) {
|
||||
if (!docs) return;
|
||||
|
||||
const convertObjectIds = (obj: any) => {
|
||||
if (!obj) return;
|
||||
|
||||
// Convert _id
|
||||
if (obj._id && obj._id.toString) {
|
||||
obj._id = obj._id.toString();
|
||||
}
|
||||
|
||||
// Convert other ObjectId fields
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (obj[key] && obj[key]._bsontype === 'ObjectId') {
|
||||
obj[key] = obj[key].toString();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (Array.isArray(docs)) {
|
||||
docs.forEach((doc) => convertObjectIds(doc));
|
||||
} else {
|
||||
convertObjectIds(docs);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return schema;
|
||||
|
|
|
|||
|
|
@ -4,3 +4,10 @@ export const readFromSecondary = {
|
|||
readPreference: ReadPreference.SECONDARY_PREFERRED, // primary | primaryPreferred | secondary | secondaryPreferred | nearest
|
||||
readConcern: 'local' as any // local | majority | linearizable | available
|
||||
};
|
||||
|
||||
export const writePrimary = {
|
||||
writeConcern: {
|
||||
w: 1,
|
||||
journal: false
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -56,3 +56,20 @@ export const delRedisCache = async (key: string) => {
|
|||
const redis = getGlobalRedisConnection();
|
||||
await retryFn(() => redis.del(getCacheKey(key)));
|
||||
};
|
||||
|
||||
export const appendRedisCache = async (
|
||||
key: string,
|
||||
value: string | Buffer | number,
|
||||
expireSeconds?: number
|
||||
) => {
|
||||
try {
|
||||
const redis = getGlobalRedisConnection();
|
||||
await retryFn(() => redis.append(getCacheKey(key), value));
|
||||
if (expireSeconds) {
|
||||
await redis.expire(getCacheKey(key), expireSeconds);
|
||||
}
|
||||
} catch (error) {
|
||||
addLog.error('Append cache error:', error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import type { NextApiResponse } from 'next';
|
|||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { proxyError, ERROR_RESPONSE, ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
|
||||
import { addLog } from '../system/log';
|
||||
import { clearCookie } from '../../support/permission/controller';
|
||||
import { replaceSensitiveText } from '@fastgpt/global/common/string/tools';
|
||||
import { UserError } from '@fastgpt/global/common/error/utils';
|
||||
import { clearCookie } from '../../support/permission/auth/common';
|
||||
|
||||
export interface ResponseType<T = any> {
|
||||
code: number;
|
||||
|
|
|
|||
|
|
@ -645,4 +645,4 @@ const createChatCompletion = async ({
|
|||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -157,23 +157,18 @@ export const onDelOneApp = async ({
|
|||
).lean();
|
||||
await Promise.all(evalJobs.map((evalJob) => removeEvaluationJob(evalJob._id)));
|
||||
|
||||
// Delete chats
|
||||
await deleteChatFiles({ appId });
|
||||
await MongoChatItem.deleteMany({
|
||||
appId
|
||||
});
|
||||
await MongoChat.deleteMany({
|
||||
appId
|
||||
});
|
||||
|
||||
const del = async (session: ClientSession) => {
|
||||
for await (const app of apps) {
|
||||
const appId = app._id;
|
||||
// Chats
|
||||
await deleteChatFiles({ appId });
|
||||
await MongoChatItem.deleteMany(
|
||||
{
|
||||
appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
await MongoChat.deleteMany(
|
||||
{
|
||||
appId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
|
||||
// 删除分享链接
|
||||
await MongoOutLink.deleteMany({
|
||||
|
|
@ -205,6 +200,7 @@ export const onDelOneApp = async ({
|
|||
{ $pull: { quickAppIds: { id: String(appId) } } }
|
||||
).session(session);
|
||||
|
||||
// Del permission
|
||||
await MongoResourcePermission.deleteMany({
|
||||
resourceType: PerResourceTypeEnum.app,
|
||||
teamId,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
|||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { MongoAppChatLog } from '../app/logs/chatLogsSchema';
|
||||
import { writePrimary } from '../../common/mongo/utils';
|
||||
|
||||
type Props = {
|
||||
chatId: string;
|
||||
|
|
@ -115,7 +116,7 @@ export async function saveChat({
|
|||
});
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
const [{ _id: chatItemIdHuman }, { _id: chatItemIdAi }] = await MongoChatItem.insertMany(
|
||||
const [{ _id: chatItemIdHuman }, { _id: chatItemIdAi }] = await MongoChatItem.create(
|
||||
processedContent.map((item) => ({
|
||||
chatId,
|
||||
teamId,
|
||||
|
|
@ -123,7 +124,7 @@ export async function saveChat({
|
|||
appId,
|
||||
...item
|
||||
})),
|
||||
{ session }
|
||||
{ session, ordered: true, ...writePrimary }
|
||||
);
|
||||
|
||||
await MongoChat.updateOne(
|
||||
|
|
@ -152,7 +153,8 @@ export async function saveChat({
|
|||
},
|
||||
{
|
||||
session,
|
||||
upsert: true
|
||||
upsert: true,
|
||||
...writePrimary
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -215,7 +217,8 @@ export async function saveChat({
|
|||
}
|
||||
},
|
||||
{
|
||||
upsert: true
|
||||
upsert: true,
|
||||
...writePrimary
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
|
|
@ -223,9 +226,15 @@ export async function saveChat({
|
|||
}
|
||||
|
||||
if (isUpdateUseTime) {
|
||||
await MongoApp.findByIdAndUpdate(appId, {
|
||||
updateTime: new Date()
|
||||
}).catch();
|
||||
await MongoApp.updateOne(
|
||||
{ _id: appId },
|
||||
{
|
||||
updateTime: new Date()
|
||||
},
|
||||
{
|
||||
...writePrimary
|
||||
}
|
||||
).catch();
|
||||
}
|
||||
} catch (error) {
|
||||
addLog.error(`update chat history error`, error);
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ export const clearExpiredDatasetImageCron = async () => {
|
|||
|
||||
for (const item of data) {
|
||||
try {
|
||||
await gridBucket.delete(item._id);
|
||||
await gridBucket.delete(new Types.ObjectId(item._id));
|
||||
} catch (error) {
|
||||
addLog.error('Delete expired dataset image error', error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"@vercel/otel": "^1.13.0",
|
||||
"@xmldom/xmldom": "^0.8.10",
|
||||
"@zilliz/milvus2-sdk-node": "2.4.10",
|
||||
"axios": "^1.8.2",
|
||||
"axios": "^1.12.1",
|
||||
"bullmq": "^5.52.2",
|
||||
"chalk": "^5.3.0",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
"mongoose": "^8.10.1",
|
||||
"multer": "2.0.2",
|
||||
"mysql2": "^3.11.3",
|
||||
"next": "14.2.28",
|
||||
"next": "14.2.32",
|
||||
"nextjs-cors": "^2.2.0",
|
||||
"node-cron": "^3.0.3",
|
||||
"node-xlsx": "^0.24.0",
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
/* Auth app permission */
|
||||
import { MongoApp } from '../../../core/app/schema';
|
||||
import { type AppDetailType } from '@fastgpt/global/core/app/type.d';
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import {
|
||||
NullRoleVal,
|
||||
PerResourceTypeEnum,
|
||||
ReadPermissionVal,
|
||||
ReadRoleVal
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
|
||||
import { getTmbInfoByTmbId } from '../../user/team/controller';
|
||||
import { getResourcePermission } from '../controller';
|
||||
import { getTmbPermission } from '../controller';
|
||||
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
|
||||
import { type PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { AppFolderTypeList, AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
|
|
@ -18,6 +18,8 @@ import { PluginSourceEnum } from '@fastgpt/global/core/app/plugin/constants';
|
|||
import { type AuthModeType, type AuthResponseType } from '../type';
|
||||
import { splitCombinePluginId } from '@fastgpt/global/core/app/plugin/utils';
|
||||
import { AppReadChatLogPerVal } from '@fastgpt/global/support/permission/app/constant';
|
||||
import { parseHeaderCert } from '../auth/common';
|
||||
import { sumPer } from '@fastgpt/global/support/permission/utils';
|
||||
|
||||
export const authPluginByTmbId = async ({
|
||||
tmbId,
|
||||
|
|
@ -90,53 +92,27 @@ export const authAppByTmbId = async ({
|
|||
|
||||
const isOwner = tmbPer.isOwner || String(app.tmbId) === String(tmbId);
|
||||
|
||||
const { Per } = await (async () => {
|
||||
if (isOwner) {
|
||||
return {
|
||||
Per: new AppPermission({ isOwner: true })
|
||||
};
|
||||
}
|
||||
const isGetParentClb =
|
||||
app.inheritPermission && !AppFolderTypeList.includes(app.type) && !!app.parentId;
|
||||
|
||||
if (
|
||||
AppFolderTypeList.includes(app.type) ||
|
||||
app.inheritPermission === false ||
|
||||
!app.parentId
|
||||
) {
|
||||
// 1. is a folder. (Folders have completely permission)
|
||||
// 2. inheritPermission is false.
|
||||
// 3. is root folder/app.
|
||||
const role = await getResourcePermission({
|
||||
teamId,
|
||||
tmbId,
|
||||
resourceId: appId,
|
||||
resourceType: PerResourceTypeEnum.app
|
||||
});
|
||||
const Per = new AppPermission({ role, isOwner });
|
||||
const [folderPer = NullRoleVal, myPer = NullRoleVal] = await Promise.all([
|
||||
isGetParentClb
|
||||
? getTmbPermission({
|
||||
teamId,
|
||||
tmbId,
|
||||
resourceId: app.parentId!,
|
||||
resourceType: PerResourceTypeEnum.app
|
||||
})
|
||||
: NullRoleVal,
|
||||
getTmbPermission({
|
||||
teamId,
|
||||
tmbId,
|
||||
resourceId: appId,
|
||||
resourceType: PerResourceTypeEnum.app
|
||||
})
|
||||
]);
|
||||
|
||||
if (app.favourite || app.quick) {
|
||||
Per.addRole(ReadRoleVal);
|
||||
}
|
||||
|
||||
return {
|
||||
Per
|
||||
};
|
||||
} else {
|
||||
// is not folder and inheritPermission is true and is not root folder.
|
||||
const { app: parent } = await authAppByTmbId({
|
||||
tmbId,
|
||||
appId: app.parentId,
|
||||
per
|
||||
});
|
||||
|
||||
const Per = new AppPermission({
|
||||
role: parent.permission.role,
|
||||
isOwner
|
||||
});
|
||||
return {
|
||||
Per
|
||||
};
|
||||
}
|
||||
})();
|
||||
const Per = new AppPermission({ role: sumPer(folderPer, myPer), isOwner });
|
||||
|
||||
if (!Per.checkPer(per)) {
|
||||
return Promise.reject(AppErrEnum.unAuthApp);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
import { parseHeaderCert } from '../controller';
|
||||
import type { ReqHeaderAuthType } from '../type';
|
||||
import { type AuthModeType } from '../type';
|
||||
import { SERVICE_LOCAL_HOST } from '../../../common/system/tools';
|
||||
import { type ApiRequestProps } from '../../../type/next';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import Cookie from 'cookie';
|
||||
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
|
||||
import { authUserSession } from '../../../support/user/session';
|
||||
import { authOpenApiKey } from '../../../support/openapi/auth';
|
||||
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export const authCert = async (props: AuthModeType) => {
|
||||
const result = await parseHeaderCert(props);
|
||||
|
|
@ -19,3 +25,149 @@ export const authRequestFromLocal = ({ req }: { req: ApiRequestProps }) => {
|
|||
return Promise.reject('Invalid request');
|
||||
}
|
||||
};
|
||||
|
||||
export async function parseHeaderCert({
|
||||
req,
|
||||
authToken = false,
|
||||
authRoot = false,
|
||||
authApiKey = false
|
||||
}: AuthModeType) {
|
||||
// parse jwt
|
||||
async function authCookieToken(cookie?: string, token?: string) {
|
||||
// 获取 cookie
|
||||
const cookies = Cookie.parse(cookie || '');
|
||||
const cookieToken = token || cookies[TokenName];
|
||||
|
||||
if (!cookieToken) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
|
||||
return { ...(await authUserSession(cookieToken)), sessionId: cookieToken };
|
||||
}
|
||||
// from authorization get apikey
|
||||
async function parseAuthorization(authorization?: string) {
|
||||
if (!authorization) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
|
||||
// Bearer fastgpt-xxxx-appId
|
||||
const auth = authorization.split(' ')[1];
|
||||
if (!auth) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
|
||||
const { apikey, appId: authorizationAppid = '' } = await (async () => {
|
||||
const arr = auth.split('-');
|
||||
// abandon
|
||||
if (arr.length === 3) {
|
||||
return {
|
||||
apikey: `${arr[0]}-${arr[1]}`,
|
||||
appId: arr[2]
|
||||
};
|
||||
}
|
||||
if (arr.length === 2) {
|
||||
return {
|
||||
apikey: auth
|
||||
};
|
||||
}
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
})();
|
||||
|
||||
// auth apikey
|
||||
const { teamId, tmbId, appId: apiKeyAppId = '', sourceName } = await authOpenApiKey({ apikey });
|
||||
|
||||
return {
|
||||
uid: '',
|
||||
teamId,
|
||||
tmbId,
|
||||
apikey,
|
||||
appId: apiKeyAppId || authorizationAppid,
|
||||
sourceName
|
||||
};
|
||||
}
|
||||
// root user
|
||||
async function parseRootKey(rootKey?: string) {
|
||||
if (!rootKey || !process.env.ROOT_KEY || rootKey !== process.env.ROOT_KEY) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
}
|
||||
|
||||
const { cookie, token, rootkey, authorization } = (req.headers || {}) as ReqHeaderAuthType;
|
||||
|
||||
const { uid, teamId, tmbId, appId, openApiKey, authType, isRoot, sourceName, sessionId } =
|
||||
await (async () => {
|
||||
if (authApiKey && authorization) {
|
||||
// apikey from authorization
|
||||
const authResponse = await parseAuthorization(authorization);
|
||||
return {
|
||||
uid: authResponse.uid,
|
||||
teamId: authResponse.teamId,
|
||||
tmbId: authResponse.tmbId,
|
||||
appId: authResponse.appId,
|
||||
openApiKey: authResponse.apikey,
|
||||
authType: AuthUserTypeEnum.apikey,
|
||||
sourceName: authResponse.sourceName
|
||||
};
|
||||
}
|
||||
if (authToken && (token || cookie)) {
|
||||
// user token(from fastgpt web)
|
||||
const res = await authCookieToken(cookie, token);
|
||||
|
||||
return {
|
||||
uid: res.userId,
|
||||
teamId: res.teamId,
|
||||
tmbId: res.tmbId,
|
||||
appId: '',
|
||||
openApiKey: '',
|
||||
authType: AuthUserTypeEnum.token,
|
||||
isRoot: res.isRoot,
|
||||
sessionId: res.sessionId
|
||||
};
|
||||
}
|
||||
if (authRoot && rootkey) {
|
||||
await parseRootKey(rootkey);
|
||||
// root user
|
||||
return {
|
||||
uid: '',
|
||||
teamId: '',
|
||||
tmbId: '',
|
||||
appId: '',
|
||||
openApiKey: '',
|
||||
authType: AuthUserTypeEnum.root,
|
||||
isRoot: true
|
||||
};
|
||||
}
|
||||
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
})();
|
||||
|
||||
if (!authRoot && (!teamId || !tmbId)) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
|
||||
return {
|
||||
userId: String(uid),
|
||||
teamId: String(teamId),
|
||||
tmbId: String(tmbId),
|
||||
appId,
|
||||
authType,
|
||||
sourceName,
|
||||
apikey: openApiKey,
|
||||
isRoot: !!isRoot,
|
||||
sessionId
|
||||
};
|
||||
}
|
||||
|
||||
/* set cookie */
|
||||
export const TokenName = 'fastgpt_token';
|
||||
export const setCookie = (res: NextApiResponse, token: string) => {
|
||||
res.setHeader(
|
||||
'Set-Cookie',
|
||||
`${TokenName}=${token}; Path=/; HttpOnly; Max-Age=604800; Samesite=Strict;`
|
||||
);
|
||||
};
|
||||
|
||||
/* clear cookie */
|
||||
export const clearCookie = (res: NextApiResponse) => {
|
||||
res.setHeader('Set-Cookie', `${TokenName}=; Path=/; Max-Age=0`);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import { type AuthModeType, type AuthResponseType } from '../type';
|
||||
import { type DatasetFileSchema } from '@fastgpt/global/core/dataset/type';
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import { getFileById } from '../../../common/file/gridfs/controller';
|
||||
import { BucketNameEnum } from '@fastgpt/global/common/file/constants';
|
||||
import { BucketNameEnum, bucketNameMap } from '@fastgpt/global/common/file/constants';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { OwnerPermissionVal, ReadRoleVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
import type { FileTokenQuery } from '@fastgpt/global/common/file/type';
|
||||
import { addMinutes } from 'date-fns';
|
||||
import { parseHeaderCert } from './common';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
|
||||
|
||||
export const authCollectionFile = async ({
|
||||
fileId,
|
||||
|
|
@ -46,3 +50,45 @@ export const authCollectionFile = async ({
|
|||
file
|
||||
};
|
||||
};
|
||||
|
||||
/* file permission */
|
||||
export const createFileToken = (data: FileTokenQuery) => {
|
||||
if (!process.env.FILE_TOKEN_KEY) {
|
||||
return Promise.reject('System unset FILE_TOKEN_KEY');
|
||||
}
|
||||
|
||||
const expireMinutes =
|
||||
data.customExpireMinutes ?? bucketNameMap[data.bucketName].previewExpireMinutes;
|
||||
const expiredTime = Math.floor(addMinutes(new Date(), expireMinutes).getTime() / 1000);
|
||||
|
||||
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';
|
||||
const token = jwt.sign(
|
||||
{
|
||||
...data,
|
||||
exp: expiredTime
|
||||
},
|
||||
key
|
||||
);
|
||||
return Promise.resolve(token);
|
||||
};
|
||||
|
||||
export const authFileToken = (token?: string) =>
|
||||
new Promise<FileTokenQuery>((resolve, reject) => {
|
||||
if (!token) {
|
||||
return reject(ERROR_ENUM.unAuthFile);
|
||||
}
|
||||
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';
|
||||
|
||||
jwt.verify(token, key, (err, decoded: any) => {
|
||||
if (err || !decoded.bucketName || !decoded?.teamId || !decoded?.fileId) {
|
||||
reject(ERROR_ENUM.unAuthFile);
|
||||
return;
|
||||
}
|
||||
resolve({
|
||||
bucketName: decoded.bucketName,
|
||||
teamId: decoded.teamId,
|
||||
uid: decoded.uid,
|
||||
fileId: decoded.fileId
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { type AuthModeType, type AuthResponseType } from '../type';
|
||||
import { type OpenApiSchema } from '@fastgpt/global/support/openapi/type';
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import { getTmbInfoByTmbId } from '../../user/team/controller';
|
||||
import { MongoOpenApi } from '../../openapi/schema';
|
||||
import { OpenApiErrEnum } from '@fastgpt/global/common/error/code/openapi';
|
||||
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { authAppByTmbId } from '../app/auth';
|
||||
import { parseHeaderCert } from './common';
|
||||
|
||||
export async function authOpenApiKeyCrud({
|
||||
id,
|
||||
|
|
|
|||
|
|
@ -1,27 +1,24 @@
|
|||
import Cookie from 'cookie';
|
||||
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { type NextApiResponse, type NextApiRequest } from 'next';
|
||||
import type { AuthModeType, ReqHeaderAuthType } from './type.d';
|
||||
import type { ClientSession, AnyBulkWriteOperation } from '../../common/mongo';
|
||||
import type { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { authOpenApiKey } from '../openapi/auth';
|
||||
import { type FileTokenQuery } from '@fastgpt/global/common/file/type';
|
||||
import { ManageRoleVal, OwnerRoleVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { MongoResourcePermission } from './schema';
|
||||
import { type ClientSession } from 'mongoose';
|
||||
import type { ResourcePermissionType, ResourceType } from '@fastgpt/global/support/permission/type';
|
||||
import { type 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';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { type MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||
import { type TeamMemberSchema } from '@fastgpt/global/support/user/team/type';
|
||||
import { type OrgSchemaType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import { getOrgIdSetWithParentByTmbId } from './org/controllers';
|
||||
import { authUserSession } from '../user/session';
|
||||
import { sumPer } from '@fastgpt/global/support/permission/utils';
|
||||
import { getCollaboratorId, sumPer } from '@fastgpt/global/support/permission/utils';
|
||||
import { type SyncChildrenPermissionResourceType } from './inheritPermission';
|
||||
import { pickCollaboratorIdFields } from './utils';
|
||||
import type {
|
||||
CollaboratorItemDetailType,
|
||||
CollaboratorItemType
|
||||
} from '@fastgpt/global/support/permission/collaborator';
|
||||
import { MongoTeamMember } from '../../support/user/team/teamMemberSchema';
|
||||
import { MongoOrgModel } from './org/orgSchema';
|
||||
import { MongoMemberGroupModel } from './memberGroup/memberGroupSchema';
|
||||
import { DEFAULT_ORG_AVATAR, DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants';
|
||||
|
||||
/** get resource permission for a team member
|
||||
* If there is no permission for the team member, it will return undefined
|
||||
|
|
@ -31,7 +28,7 @@ import { sumPer } from '@fastgpt/global/support/permission/utils';
|
|||
* @param resourceId
|
||||
* @returns PermissionValueType | undefined
|
||||
*/
|
||||
export const getResourcePermission = async ({
|
||||
export const getTmbPermission = async ({
|
||||
resourceType,
|
||||
teamId,
|
||||
tmbId,
|
||||
|
|
@ -106,17 +103,27 @@ export const getResourcePermission = async ({
|
|||
return sumPer(...groupPers, ...orgPers);
|
||||
};
|
||||
|
||||
export async function getResourceClbsAndGroups({
|
||||
resourceId,
|
||||
/**
|
||||
* Only get resource's owned clbs, not including parents'.
|
||||
*/
|
||||
export async function getResourceOwnedClbs({
|
||||
resourceType,
|
||||
teamId,
|
||||
resourceId,
|
||||
session
|
||||
}: {
|
||||
resourceId: ParentIdType;
|
||||
resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>;
|
||||
teamId: string;
|
||||
session: ClientSession;
|
||||
}) {
|
||||
session?: ClientSession;
|
||||
} & (
|
||||
| {
|
||||
resourceType: 'team';
|
||||
resourceId?: undefined;
|
||||
}
|
||||
| {
|
||||
resourceType: Omit<PerResourceTypeEnum, 'team'>;
|
||||
resourceId: ParentIdType;
|
||||
}
|
||||
)) {
|
||||
return MongoResourcePermission.find(
|
||||
{
|
||||
resourceId,
|
||||
|
|
@ -124,282 +131,110 @@ export async function getResourceClbsAndGroups({
|
|||
teamId
|
||||
},
|
||||
undefined,
|
||||
{ session }
|
||||
{ ...(session ? { session } : {}) }
|
||||
).lean();
|
||||
}
|
||||
|
||||
export const getClbsAndGroupsWithInfo = async ({
|
||||
resourceId,
|
||||
resourceType,
|
||||
teamId
|
||||
export const getClbsInfo = async ({
|
||||
clbs,
|
||||
teamId,
|
||||
ownerTmbId
|
||||
}: {
|
||||
clbs: CollaboratorItemType[];
|
||||
teamId: string;
|
||||
} & (
|
||||
| {
|
||||
resourceId: ParentIdType;
|
||||
resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>;
|
||||
}
|
||||
| {
|
||||
resourceType: 'team';
|
||||
resourceId?: undefined;
|
||||
}
|
||||
)) =>
|
||||
Promise.all([
|
||||
MongoResourcePermission.find({
|
||||
teamId,
|
||||
resourceId,
|
||||
resourceType,
|
||||
tmbId: {
|
||||
$exists: true
|
||||
}
|
||||
})
|
||||
.populate<{ tmb: TeamMemberSchema }>({
|
||||
path: 'tmb',
|
||||
select: 'name userId avatar'
|
||||
})
|
||||
.lean(),
|
||||
MongoResourcePermission.find({
|
||||
teamId,
|
||||
resourceId,
|
||||
resourceType,
|
||||
groupId: {
|
||||
$exists: true
|
||||
}
|
||||
})
|
||||
.populate<{ group: MemberGroupSchemaType }>('group', 'name avatar')
|
||||
.lean(),
|
||||
MongoResourcePermission.find({
|
||||
teamId,
|
||||
resourceId,
|
||||
resourceType,
|
||||
orgId: {
|
||||
$exists: true
|
||||
}
|
||||
})
|
||||
.populate<{ org: OrgSchemaType }>({ path: 'org', select: 'name avatar' })
|
||||
.lean()
|
||||
]);
|
||||
ownerTmbId?: string;
|
||||
}): Promise<CollaboratorItemDetailType[]> => {
|
||||
const tmbIds = [];
|
||||
const orgIds = [];
|
||||
const groupIds = [];
|
||||
|
||||
export const delResourcePermissionById = (id: string) => {
|
||||
return MongoResourcePermission.findByIdAndDelete(id);
|
||||
};
|
||||
export const delResourcePermission = ({
|
||||
session,
|
||||
tmbId,
|
||||
groupId,
|
||||
orgId,
|
||||
...props
|
||||
}: {
|
||||
resourceType: PerResourceTypeEnum;
|
||||
teamId: string;
|
||||
resourceId: string;
|
||||
session?: ClientSession;
|
||||
tmbId?: string;
|
||||
groupId?: string;
|
||||
orgId?: string;
|
||||
}) => {
|
||||
// either tmbId or groupId or orgId must be provided
|
||||
if (!tmbId && !groupId && !orgId) {
|
||||
return Promise.reject(CommonErrEnum.missingParams);
|
||||
for (const clb of clbs) {
|
||||
if (clb.tmbId) tmbIds.push(clb.tmbId);
|
||||
if (clb.orgId) orgIds.push(clb.orgId);
|
||||
if (clb.groupId) groupIds.push(clb.groupId);
|
||||
}
|
||||
|
||||
return MongoResourcePermission.deleteOne(
|
||||
{
|
||||
...(tmbId ? { tmbId } : {}),
|
||||
...(groupId ? { groupId } : {}),
|
||||
...(orgId ? { orgId } : {}),
|
||||
...props
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
};
|
||||
const infos = (
|
||||
await Promise.all([
|
||||
MongoTeamMember.find({ _id: { $in: tmbIds }, teamId }, '_id name avatar').lean(),
|
||||
MongoOrgModel.find({ _id: { $in: orgIds }, teamId }, '_id name avatar').lean(),
|
||||
MongoMemberGroupModel.find({ _id: { $in: groupIds }, teamId }, '_id name avatar').lean()
|
||||
])
|
||||
).flat();
|
||||
|
||||
/* 下面代码等迁移 */
|
||||
|
||||
export async function parseHeaderCert({
|
||||
req,
|
||||
authToken = false,
|
||||
authRoot = false,
|
||||
authApiKey = false
|
||||
}: AuthModeType) {
|
||||
// parse jwt
|
||||
async function authCookieToken(cookie?: string, token?: string) {
|
||||
// 获取 cookie
|
||||
const cookies = Cookie.parse(cookie || '');
|
||||
const cookieToken = token || cookies[TokenName];
|
||||
|
||||
if (!cookieToken) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
|
||||
return { ...(await authUserSession(cookieToken)), sessionId: cookieToken };
|
||||
}
|
||||
// from authorization get apikey
|
||||
async function parseAuthorization(authorization?: string) {
|
||||
if (!authorization) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
|
||||
// Bearer fastgpt-xxxx-appId
|
||||
const auth = authorization.split(' ')[1];
|
||||
if (!auth) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
|
||||
const { apikey, appId: authorizationAppid = '' } = await (async () => {
|
||||
const arr = auth.split('-');
|
||||
// abandon
|
||||
if (arr.length === 3) {
|
||||
return {
|
||||
apikey: `${arr[0]}-${arr[1]}`,
|
||||
appId: arr[2]
|
||||
};
|
||||
}
|
||||
if (arr.length === 2) {
|
||||
return {
|
||||
apikey: auth
|
||||
};
|
||||
}
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
})();
|
||||
|
||||
// auth apikey
|
||||
const { teamId, tmbId, appId: apiKeyAppId = '', sourceName } = await authOpenApiKey({ apikey });
|
||||
return clbs.map((clb) => {
|
||||
const info = infos.find((info) => info._id === getCollaboratorId(clb));
|
||||
|
||||
return {
|
||||
uid: '',
|
||||
...clb,
|
||||
teamId,
|
||||
tmbId,
|
||||
apikey,
|
||||
appId: apiKeyAppId || authorizationAppid,
|
||||
sourceName
|
||||
permission: new Permission({
|
||||
role: clb.permission,
|
||||
isOwner: Boolean(ownerTmbId && clb.tmbId && ownerTmbId === clb.tmbId)
|
||||
}),
|
||||
name: info?.name ?? 'Unknown name',
|
||||
avatar: info?.avatar || (clb.orgId ? DEFAULT_ORG_AVATAR : DEFAULT_TEAM_AVATAR)
|
||||
};
|
||||
}
|
||||
// root user
|
||||
async function parseRootKey(rootKey?: string) {
|
||||
if (!rootKey || !process.env.ROOT_KEY || rootKey !== process.env.ROOT_KEY) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
}
|
||||
|
||||
const { cookie, token, rootkey, authorization } = (req.headers || {}) as ReqHeaderAuthType;
|
||||
|
||||
const { uid, teamId, tmbId, appId, openApiKey, authType, isRoot, sourceName, sessionId } =
|
||||
await (async () => {
|
||||
if (authApiKey && authorization) {
|
||||
// apikey from authorization
|
||||
const authResponse = await parseAuthorization(authorization);
|
||||
return {
|
||||
uid: authResponse.uid,
|
||||
teamId: authResponse.teamId,
|
||||
tmbId: authResponse.tmbId,
|
||||
appId: authResponse.appId,
|
||||
openApiKey: authResponse.apikey,
|
||||
authType: AuthUserTypeEnum.apikey,
|
||||
sourceName: authResponse.sourceName
|
||||
};
|
||||
}
|
||||
if (authToken && (token || cookie)) {
|
||||
// user token(from fastgpt web)
|
||||
const res = await authCookieToken(cookie, token);
|
||||
|
||||
return {
|
||||
uid: res.userId,
|
||||
teamId: res.teamId,
|
||||
tmbId: res.tmbId,
|
||||
appId: '',
|
||||
openApiKey: '',
|
||||
authType: AuthUserTypeEnum.token,
|
||||
isRoot: res.isRoot,
|
||||
sessionId: res.sessionId
|
||||
};
|
||||
}
|
||||
if (authRoot && rootkey) {
|
||||
await parseRootKey(rootkey);
|
||||
// root user
|
||||
return {
|
||||
uid: '',
|
||||
teamId: '',
|
||||
tmbId: '',
|
||||
appId: '',
|
||||
openApiKey: '',
|
||||
authType: AuthUserTypeEnum.root,
|
||||
isRoot: true
|
||||
};
|
||||
}
|
||||
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
})();
|
||||
|
||||
if (!authRoot && (!teamId || !tmbId)) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
|
||||
return {
|
||||
userId: String(uid),
|
||||
teamId: String(teamId),
|
||||
tmbId: String(tmbId),
|
||||
appId,
|
||||
authType,
|
||||
sourceName,
|
||||
apikey: openApiKey,
|
||||
isRoot: !!isRoot,
|
||||
sessionId
|
||||
};
|
||||
}
|
||||
|
||||
/* set cookie */
|
||||
export const TokenName = 'fastgpt_token';
|
||||
export const setCookie = (res: NextApiResponse, token: string) => {
|
||||
res.setHeader(
|
||||
'Set-Cookie',
|
||||
`${TokenName}=${token}; Path=/; HttpOnly; Max-Age=604800; Samesite=Strict;`
|
||||
);
|
||||
};
|
||||
|
||||
/* clear cookie */
|
||||
export const clearCookie = (res: NextApiResponse) => {
|
||||
res.setHeader('Set-Cookie', `${TokenName}=; Path=/; Max-Age=0`);
|
||||
};
|
||||
|
||||
/* file permission */
|
||||
export const createFileToken = (data: FileTokenQuery) => {
|
||||
if (!process.env.FILE_TOKEN_KEY) {
|
||||
return Promise.reject('System unset FILE_TOKEN_KEY');
|
||||
}
|
||||
|
||||
const expireMinutes =
|
||||
data.customExpireMinutes ?? bucketNameMap[data.bucketName].previewExpireMinutes;
|
||||
const expiredTime = Math.floor(addMinutes(new Date(), expireMinutes).getTime() / 1000);
|
||||
|
||||
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';
|
||||
const token = jwt.sign(
|
||||
{
|
||||
...data,
|
||||
exp: expiredTime
|
||||
},
|
||||
key
|
||||
);
|
||||
return Promise.resolve(token);
|
||||
};
|
||||
|
||||
export const authFileToken = (token?: string) =>
|
||||
new Promise<FileTokenQuery>((resolve, reject) => {
|
||||
if (!token) {
|
||||
return reject(ERROR_ENUM.unAuthFile);
|
||||
}
|
||||
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';
|
||||
|
||||
jwt.verify(token, key, (err, decoded: any) => {
|
||||
if (err || !decoded.bucketName || !decoded?.teamId || !decoded?.fileId) {
|
||||
reject(ERROR_ENUM.unAuthFile);
|
||||
return;
|
||||
}
|
||||
resolve({
|
||||
bucketName: decoded.bucketName,
|
||||
teamId: decoded.teamId,
|
||||
uid: decoded.uid,
|
||||
fileId: decoded.fileId
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const createResourceDefaultCollaborators = async ({
|
||||
resource,
|
||||
resourceType,
|
||||
session,
|
||||
tmbId
|
||||
}: {
|
||||
resource: SyncChildrenPermissionResourceType;
|
||||
resourceType: PerResourceTypeEnum;
|
||||
|
||||
// should be provided when inheritPermission is true
|
||||
session: ClientSession;
|
||||
tmbId: string;
|
||||
}) => {
|
||||
const parentClbs = await getResourceOwnedClbs({
|
||||
resourceId: resource.parentId,
|
||||
resourceType,
|
||||
teamId: resource.teamId,
|
||||
session
|
||||
});
|
||||
// 1. add owner into the permission list with owner per
|
||||
// 2. remove parent's owner permission, instead of manager
|
||||
|
||||
const collaborators: CollaboratorItemType[] = [
|
||||
...parentClbs
|
||||
.filter((item) => item.tmbId !== tmbId)
|
||||
.map((clb) => {
|
||||
if (clb.permission === OwnerRoleVal) {
|
||||
clb.permission = ManageRoleVal;
|
||||
}
|
||||
return clb;
|
||||
}),
|
||||
{
|
||||
tmbId,
|
||||
permission: OwnerRoleVal
|
||||
}
|
||||
];
|
||||
|
||||
const ops: AnyBulkWriteOperation<ResourcePermissionType>[] = [];
|
||||
|
||||
for (const clb of collaborators) {
|
||||
ops.push({
|
||||
updateOne: {
|
||||
filter: {
|
||||
...pickCollaboratorIdFields(clb),
|
||||
teamId: resource.teamId,
|
||||
resourceId: resource._id,
|
||||
resourceType
|
||||
},
|
||||
update: {
|
||||
$set: {
|
||||
permission: clb.permission
|
||||
}
|
||||
},
|
||||
upsert: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await MongoResourcePermission.bulkWrite(ops, { session });
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { type PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { getResourcePermission, parseHeaderCert } from '../controller';
|
||||
import { getTmbPermission } from '../controller';
|
||||
import {
|
||||
type CollectionWithDatasetType,
|
||||
type DatasetDataItemType,
|
||||
|
|
@ -9,6 +9,7 @@ import { getTmbInfoByTmbId } from '../../user/team/controller';
|
|||
import { MongoDataset } from '../../../core/dataset/schema';
|
||||
import {
|
||||
NullPermissionVal,
|
||||
NullRoleVal,
|
||||
PerResourceTypeEnum
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
|
|
@ -21,6 +22,8 @@ import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
|||
import { DataSetDefaultRoleVal } from '@fastgpt/global/support/permission/dataset/constant';
|
||||
import { getDatasetImagePreviewUrl } from '../../../core/dataset/image/utils';
|
||||
import { i18nT } from '../../../../web/i18n/utils';
|
||||
import { parseHeaderCert } from '../auth/common';
|
||||
import { sumPer } from '@fastgpt/global/support/permission/utils';
|
||||
|
||||
export const authDatasetByTmbId = async ({
|
||||
tmbId,
|
||||
|
|
@ -61,54 +64,27 @@ export const authDatasetByTmbId = async ({
|
|||
}
|
||||
|
||||
const isOwner = tmbPer.isOwner || String(dataset.tmbId) === String(tmbId);
|
||||
const isGetParentClb =
|
||||
dataset.inheritPermission && dataset.type !== DatasetTypeEnum.folder && !!dataset.parentId;
|
||||
|
||||
// get dataset permission or inherit permission from parent folder.
|
||||
const { Per } = await (async () => {
|
||||
if (isOwner) {
|
||||
return {
|
||||
Per: new DatasetPermission({ isOwner: true })
|
||||
};
|
||||
}
|
||||
if (
|
||||
dataset.type === DatasetTypeEnum.folder ||
|
||||
dataset.inheritPermission === false ||
|
||||
!dataset.parentId
|
||||
) {
|
||||
// 1. is a folder. (Folders have completely permission)
|
||||
// 2. inheritPermission is false.
|
||||
// 3. is root folder/dataset.
|
||||
const rp = await getResourcePermission({
|
||||
teamId,
|
||||
tmbId,
|
||||
resourceId: datasetId,
|
||||
resourceType: PerResourceTypeEnum.dataset
|
||||
});
|
||||
const Per = new DatasetPermission({
|
||||
role: rp,
|
||||
isOwner
|
||||
});
|
||||
return {
|
||||
Per
|
||||
};
|
||||
} else {
|
||||
// is not folder and inheritPermission is true and is not root folder.
|
||||
const { dataset: parent } = await authDatasetByTmbId({
|
||||
tmbId,
|
||||
datasetId: dataset.parentId,
|
||||
per,
|
||||
isRoot
|
||||
});
|
||||
const [folderPer = NullRoleVal, myPer = NullRoleVal] = await Promise.all([
|
||||
isGetParentClb
|
||||
? getTmbPermission({
|
||||
teamId,
|
||||
tmbId,
|
||||
resourceId: dataset.parentId!,
|
||||
resourceType: PerResourceTypeEnum.dataset
|
||||
})
|
||||
: NullRoleVal,
|
||||
getTmbPermission({
|
||||
teamId,
|
||||
tmbId,
|
||||
resourceId: datasetId,
|
||||
resourceType: PerResourceTypeEnum.dataset
|
||||
})
|
||||
]);
|
||||
|
||||
const Per = new DatasetPermission({
|
||||
role: parent.permission.role,
|
||||
isOwner
|
||||
});
|
||||
|
||||
return {
|
||||
Per
|
||||
};
|
||||
}
|
||||
})();
|
||||
const Per = new DatasetPermission({ role: sumPer(folderPer, myPer), isOwner });
|
||||
|
||||
if (!Per.checkPer(per)) {
|
||||
return Promise.reject(DatasetErrEnum.unAuthDataset);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { parseHeaderCert } from '../controller';
|
||||
import { authAppByTmbId } from '../app/auth';
|
||||
import {
|
||||
ManagePermissionVal,
|
||||
|
|
@ -7,6 +6,7 @@ import {
|
|||
import type { EvaluationSchemaType } from '@fastgpt/global/core/app/evaluation/type';
|
||||
import type { AuthModeType } from '../type';
|
||||
import { MongoEvaluation } from '../../../core/app/evaluation/evalSchema';
|
||||
import { parseHeaderCert } from '../auth/common';
|
||||
|
||||
export const authEval = async ({
|
||||
evalId,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,22 @@
|
|||
import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
import type { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import type { ClientSession, Model } from 'mongoose';
|
||||
import {
|
||||
ManageRoleVal,
|
||||
NullPermissionVal,
|
||||
OwnerRoleVal,
|
||||
type PerResourceTypeEnum
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import type { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
|
||||
import { mongoSessionRun } from '../../common/mongo/sessionRun';
|
||||
import { getResourceClbsAndGroups } from './controller';
|
||||
import { getResourceOwnedClbs } from './controller';
|
||||
import { MongoResourcePermission } from './schema';
|
||||
import type { ClientSession, Model, AnyBulkWriteOperation } from '../../common/mongo';
|
||||
import {
|
||||
getCollaboratorId,
|
||||
mergeCollaboratorList,
|
||||
sumPer
|
||||
} from '@fastgpt/global/support/permission/utils';
|
||||
import type { CollaboratorItemType } from '@fastgpt/global/support/permission/collaborator';
|
||||
import { pickCollaboratorIdFields } from './utils';
|
||||
|
||||
export type SyncChildrenPermissionResourceType = {
|
||||
_id: string;
|
||||
|
|
@ -13,15 +24,10 @@ export type SyncChildrenPermissionResourceType = {
|
|||
teamId: string;
|
||||
parentId?: ParentIdType;
|
||||
};
|
||||
export type UpdateCollaboratorItem = {
|
||||
permission: PermissionValueType;
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
// sync the permission to all children folders.
|
||||
/**
|
||||
* sync the permission to all children folders.
|
||||
*/
|
||||
export async function syncChildrenPermission({
|
||||
resource,
|
||||
folderTypeList,
|
||||
|
|
@ -29,7 +35,7 @@ export async function syncChildrenPermission({
|
|||
resourceModel,
|
||||
session,
|
||||
|
||||
collaborators
|
||||
collaborators: latestClbList
|
||||
}: {
|
||||
resource: SyncChildrenPermissionResourceType;
|
||||
|
||||
|
|
@ -42,55 +48,155 @@ export async function syncChildrenPermission({
|
|||
// should be provided when inheritPermission is true
|
||||
session: ClientSession;
|
||||
|
||||
collaborators?: UpdateCollaboratorItem[];
|
||||
collaborators: CollaboratorItemType[];
|
||||
}) {
|
||||
// only folder has permission
|
||||
const isFolder = folderTypeList.includes(resource.type);
|
||||
const teamId = resource.teamId;
|
||||
|
||||
// If the 'root' is not a folder, which means the 'root' has no children, no need to sync.
|
||||
if (!isFolder) return;
|
||||
|
||||
// get all folders and the resource permission of the app
|
||||
// get all the resource permission of the app
|
||||
const allFolders = await resourceModel
|
||||
.find(
|
||||
{
|
||||
teamId: resource.teamId,
|
||||
type: { $in: folderTypeList },
|
||||
inheritPermission: true
|
||||
teamId,
|
||||
inheritPermission: true,
|
||||
type: {
|
||||
$in: folderTypeList
|
||||
}
|
||||
},
|
||||
'_id parentId'
|
||||
)
|
||||
.lean<SyncChildrenPermissionResourceType[]>()
|
||||
.session(session);
|
||||
|
||||
// bfs to get all children
|
||||
const queue = [String(resource._id)];
|
||||
const children: string[] = [];
|
||||
while (queue.length) {
|
||||
const parentId = queue.shift();
|
||||
const folderChildren = allFolders.filter(
|
||||
(folder) => String(folder.parentId) === String(parentId)
|
||||
);
|
||||
children.push(...folderChildren.map((folder) => folder._id));
|
||||
queue.push(...folderChildren.map((folder) => folder._id));
|
||||
}
|
||||
if (!children.length) return;
|
||||
const allClbs = await MongoResourcePermission.find({
|
||||
resourceType,
|
||||
teamId,
|
||||
resourceId: {
|
||||
$in: allFolders.map((folder) => folder._id)
|
||||
}
|
||||
})
|
||||
.lean()
|
||||
.session(session);
|
||||
|
||||
// sync the resource permission
|
||||
if (collaborators) {
|
||||
// Update the collaborators of all children
|
||||
for await (const childId of children) {
|
||||
await syncCollaborators({
|
||||
resourceType,
|
||||
session,
|
||||
collaborators,
|
||||
teamId: resource.teamId,
|
||||
resourceId: childId
|
||||
});
|
||||
/** ResourceMap<resourceId, resourceType> */
|
||||
const resourceMap = new Map<string, SyncChildrenPermissionResourceType>();
|
||||
/** parentChildrenMap<parentId, resourceType[]> */
|
||||
const parentChildrenMap = new Map<string, SyncChildrenPermissionResourceType[]>();
|
||||
|
||||
// init the map
|
||||
allFolders.forEach((resource) => {
|
||||
resourceMap.set(resource._id, resource);
|
||||
const parentId = String(resource.parentId);
|
||||
if (!parentChildrenMap.has(parentId)) {
|
||||
parentChildrenMap.set(parentId, []);
|
||||
}
|
||||
parentChildrenMap.get(parentId)!.push(resource);
|
||||
});
|
||||
|
||||
/** resourceIdPermissionMap<resourceId, CollaboratorItemType[]>
|
||||
* save the clb virtual state, not the real state at present in the DB.
|
||||
*/
|
||||
const resourceIdClbMap = new Map<string, ResourcePermissionType[]>();
|
||||
|
||||
// Initialize the resourceIdPermissionMap
|
||||
for (const clb of allClbs) {
|
||||
const resourceId = clb.resourceId;
|
||||
const arr = resourceIdClbMap.get(resourceId);
|
||||
if (!arr) {
|
||||
resourceIdClbMap.set(resourceId, [clb]);
|
||||
} else {
|
||||
arr.push(clb);
|
||||
}
|
||||
}
|
||||
|
||||
// BFS to get all children
|
||||
const queue = [String(resource._id)];
|
||||
const ops: AnyBulkWriteOperation<ResourcePermissionType>[] = [];
|
||||
const latestClbMap = new Map(latestClbList.map((clb) => [getCollaboratorId(clb), { ...clb }]));
|
||||
|
||||
while (queue.length) {
|
||||
const parentId = String(queue.shift());
|
||||
const _children = parentChildrenMap.get(parentId) || [];
|
||||
if (_children.length === 0) continue;
|
||||
for (const child of _children) {
|
||||
// 1. get parent's permission and what permission I have.
|
||||
const parentClbs = resourceIdClbMap.get(String(child.parentId)) || [];
|
||||
const myClbs = resourceIdClbMap.get(child._id) || [];
|
||||
const myClbsIdSet = new Set(myClbs.map((clb) => getCollaboratorId(clb)));
|
||||
|
||||
// add or update
|
||||
for (const latestClb of latestClbList) {
|
||||
if (latestClb.permission === OwnerRoleVal) {
|
||||
continue;
|
||||
}
|
||||
if (!myClbsIdSet.has(getCollaboratorId(latestClb))) {
|
||||
ops.push({
|
||||
insertOne: {
|
||||
document: {
|
||||
resourceId: child._id,
|
||||
resourceType,
|
||||
teamId,
|
||||
permission: latestClb.permission,
|
||||
...pickCollaboratorIdFields(latestClb)
|
||||
} as ResourcePermissionType
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const myclb = myClbs.find(
|
||||
(clb) => getCollaboratorId(latestClb) === getCollaboratorId(clb)
|
||||
)!;
|
||||
ops.push({
|
||||
updateOne: {
|
||||
filter: {
|
||||
resourceId: child._id,
|
||||
teamId,
|
||||
...pickCollaboratorIdFields(latestClb),
|
||||
resourceType
|
||||
},
|
||||
update: {
|
||||
permission: sumPer(myclb.permission, latestClb.permission)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// delele
|
||||
for (const myClb of myClbs) {
|
||||
const parentClb = parentClbs.find(
|
||||
(clb) => getCollaboratorId(clb) === getCollaboratorId(myClb)
|
||||
);
|
||||
// the new collaborators doesnt have it, and the permission is same.
|
||||
// remove it
|
||||
if (
|
||||
!latestClbMap.get(getCollaboratorId(myClb)) &&
|
||||
parentClb &&
|
||||
myClb.permission === parentClb.permission
|
||||
) {
|
||||
ops.push({
|
||||
deleteOne: {
|
||||
filter: {
|
||||
resourceId: child._id,
|
||||
teamId,
|
||||
...pickCollaboratorIdFields(myClb),
|
||||
resourceType
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
queue.push(child._id);
|
||||
}
|
||||
}
|
||||
await MongoResourcePermission.bulkWrite(ops, { session });
|
||||
return;
|
||||
}
|
||||
|
||||
/* Resume the inherit permission of the resource.
|
||||
/** Resume the inherit permission of the resource.
|
||||
1. Folder: Sync parent's defaultPermission and clbs, and sync its children.
|
||||
2. Resource: Sync parent's defaultPermission, and delete all its clbs.
|
||||
*/
|
||||
|
|
@ -108,9 +214,54 @@ export async function resumeInheritPermission({
|
|||
session?: ClientSession;
|
||||
}) {
|
||||
const isFolder = folderTypeList.includes(resource.type);
|
||||
// Folder resource, need to sync children
|
||||
const [parentClbs, oldMyClbs] = await Promise.all([
|
||||
getResourceOwnedClbs({
|
||||
resourceId: resource.parentId,
|
||||
teamId: resource.teamId,
|
||||
resourceType
|
||||
}),
|
||||
getResourceOwnedClbs({
|
||||
resourceId: resource._id,
|
||||
teamId: resource.teamId,
|
||||
resourceType
|
||||
})
|
||||
]);
|
||||
|
||||
const parentOwner = parentClbs.find((clb) => clb.permission === OwnerRoleVal);
|
||||
|
||||
const collaborators = mergeCollaboratorList({
|
||||
parentClbs,
|
||||
childClbs: oldMyClbs
|
||||
});
|
||||
const parentManage = collaborators.find(
|
||||
(clb) => parentOwner?.tmbId && clb.tmbId && parentOwner?.tmbId === clb.tmbId
|
||||
);
|
||||
if (parentManage) parentManage.permission = ManageRoleVal;
|
||||
|
||||
console.log(collaborators);
|
||||
|
||||
const fn = async (session: ClientSession) => {
|
||||
// update the resource permission
|
||||
if (isFolder) {
|
||||
// sync self
|
||||
await syncCollaborators({
|
||||
resourceType,
|
||||
collaborators,
|
||||
teamId: resource.teamId,
|
||||
resourceId: resource._id,
|
||||
session
|
||||
});
|
||||
// sync children
|
||||
await syncChildrenPermission({
|
||||
resource,
|
||||
resourceModel,
|
||||
folderTypeList,
|
||||
resourceType,
|
||||
session,
|
||||
collaborators
|
||||
});
|
||||
}
|
||||
|
||||
await resourceModel.updateOne(
|
||||
{
|
||||
_id: resource._id
|
||||
|
|
@ -120,39 +271,6 @@ export async function resumeInheritPermission({
|
|||
},
|
||||
{ session }
|
||||
);
|
||||
|
||||
// Folder resource, need to sync children
|
||||
if (isFolder) {
|
||||
const parentClbsAndGroups = await getResourceClbsAndGroups({
|
||||
resourceId: resource.parentId,
|
||||
teamId: resource.teamId,
|
||||
resourceType,
|
||||
session
|
||||
});
|
||||
|
||||
// sync self
|
||||
await syncCollaborators({
|
||||
resourceType,
|
||||
collaborators: parentClbsAndGroups,
|
||||
teamId: resource.teamId,
|
||||
resourceId: resource._id,
|
||||
session
|
||||
});
|
||||
// sync children
|
||||
await syncChildrenPermission({
|
||||
resource: {
|
||||
...resource
|
||||
},
|
||||
resourceModel,
|
||||
folderTypeList,
|
||||
resourceType,
|
||||
session,
|
||||
collaborators: parentClbsAndGroups
|
||||
});
|
||||
} else {
|
||||
// Not folder, delete all clb
|
||||
await MongoResourcePermission.deleteMany({ resourceId: resource._id }, { session });
|
||||
}
|
||||
};
|
||||
|
||||
if (session) {
|
||||
|
|
@ -162,9 +280,9 @@ export async function resumeInheritPermission({
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Delete all the collaborators and then insert the new collaborators.
|
||||
*/
|
||||
/**
|
||||
* sync parent collaborators to children.
|
||||
*/
|
||||
export async function syncCollaborators({
|
||||
resourceType,
|
||||
teamId,
|
||||
|
|
@ -175,30 +293,59 @@ export async function syncCollaborators({
|
|||
resourceType: PerResourceTypeEnum;
|
||||
teamId: string;
|
||||
resourceId: string;
|
||||
collaborators: UpdateCollaboratorItem[];
|
||||
collaborators: CollaboratorItemType[];
|
||||
session: ClientSession;
|
||||
}) {
|
||||
await MongoResourcePermission.deleteMany(
|
||||
{
|
||||
resourceType,
|
||||
teamId,
|
||||
resourceId
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
await MongoResourcePermission.insertMany(
|
||||
collaborators.map((item) => ({
|
||||
teamId: teamId,
|
||||
resourceId,
|
||||
resourceType: resourceType,
|
||||
tmbId: item.tmbId,
|
||||
groupId: item.groupId,
|
||||
orgId: item.orgId,
|
||||
permission: item.permission
|
||||
})),
|
||||
{
|
||||
session,
|
||||
ordered: true
|
||||
// should change parent owner permission into manage
|
||||
collaborators.forEach((clb) => {
|
||||
if (clb.permission === OwnerRoleVal) {
|
||||
clb.permission = ManageRoleVal;
|
||||
}
|
||||
});
|
||||
const parentClbMap = new Map(collaborators.map((clb) => [getCollaboratorId(clb), clb]));
|
||||
const clbsNow = await MongoResourcePermission.find({
|
||||
resourceType,
|
||||
teamId,
|
||||
resourceId
|
||||
})
|
||||
.lean()
|
||||
.session(session);
|
||||
const ops: AnyBulkWriteOperation<ResourcePermissionType>[] = [];
|
||||
for (const clb of clbsNow) {
|
||||
const parentClb = parentClbMap.get(getCollaboratorId(clb));
|
||||
const permission = sumPer(parentClb?.permission ?? NullPermissionVal, clb.permission);
|
||||
ops.push({
|
||||
updateOne: {
|
||||
filter: {
|
||||
teamId,
|
||||
resourceId,
|
||||
resourceType,
|
||||
...pickCollaboratorIdFields(clb)
|
||||
},
|
||||
update: {
|
||||
permission
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const parentHasAndIHaveNot = collaborators.filter(
|
||||
(clb) => !clbsNow.some((myClb) => getCollaboratorId(clb) === getCollaboratorId(myClb))
|
||||
);
|
||||
|
||||
for (const clb of parentHasAndIHaveNot) {
|
||||
ops.push({
|
||||
insertOne: {
|
||||
document: {
|
||||
teamId,
|
||||
resourceId,
|
||||
resourceType,
|
||||
...pickCollaboratorIdFields(clb),
|
||||
permission: clb.permission
|
||||
} as ResourcePermissionType
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await MongoResourcePermission.bulkWrite(ops, { session });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { type MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||
import { MongoGroupMemberModel } from './groupMemberSchema';
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import { MongoMemberGroupModel } from './memberGroupSchema';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import { type ClientSession } from 'mongoose';
|
||||
|
|
@ -9,6 +8,7 @@ import { type AuthModeType, type AuthResponseType } from '../type';
|
|||
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
||||
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
|
||||
import { getTmbInfoByTmbId } from '../../user/team/controller';
|
||||
import { parseHeaderCert } from '../auth/common';
|
||||
|
||||
/**
|
||||
* Get the default group of a team
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ 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';
|
||||
import { DEFAULT_ORG_AVATAR } from '@fastgpt/global/common/system/constants';
|
||||
const { Schema } = connectionMongo;
|
||||
|
||||
export const OrgSchema = new Schema(
|
||||
|
|
@ -29,7 +30,9 @@ export const OrgSchema = new Schema(
|
|||
type: String,
|
||||
required: true
|
||||
},
|
||||
avatar: String,
|
||||
avatar: {
|
||||
type: String
|
||||
},
|
||||
description: String,
|
||||
updateTime: {
|
||||
type: Date,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { type AppDetailType } from '@fastgpt/global/core/app/type';
|
||||
import { type OutlinkAppType, type OutLinkSchema } from '@fastgpt/global/support/outLink/type';
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import { MongoOutLink } from '../../outLink/schema';
|
||||
import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink';
|
||||
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { authAppByTmbId } from '../app/auth';
|
||||
import { type AuthModeType, type AuthResponseType } from '../type';
|
||||
import { parseHeaderCert } from '../auth/common';
|
||||
|
||||
/* crud outlink permission */
|
||||
export async function authOutLinkCrud({
|
||||
|
|
|
|||
|
|
@ -34,11 +34,18 @@ export const ResourcePermissionSchema = new Schema({
|
|||
enum: Object.values(PerResourceTypeEnum),
|
||||
required: true
|
||||
},
|
||||
/**
|
||||
* The **Role** of the object to the resource.
|
||||
*/
|
||||
permission: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
// Resrouce ID: App or DataSet or any other resource type.
|
||||
/**
|
||||
* Optional. Only be set when the resource is *inherited* from the parent resource.
|
||||
* For recording the self permission. When cancel the inheritance, it will overwrite the permission property and set to `unset`.
|
||||
*/
|
||||
// Resource ID: App or DataSet or any other resource type.
|
||||
// It is null if the resourceType is team.
|
||||
resourceId: {
|
||||
type: Schema.Types.ObjectId
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import { type TeamTmbItemType } from '@fastgpt/global/support/user/team/type';
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import { getTmbInfoByTmbId } from '../../user/team/controller';
|
||||
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
||||
import { type AuthModeType, type AuthResponseType } from '../type';
|
||||
import { NullPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
|
||||
import { authCert } from '../auth/common';
|
||||
import { authCert, parseHeaderCert } from '../auth/common';
|
||||
import { MongoUser } from '../../user/schema';
|
||||
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
|
||||
import { type ApiRequestProps } from '../../../type/next';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import type { CollaboratorIdType } from '@fastgpt/global/support/permission/collaborator';
|
||||
|
||||
export const pickCollaboratorIdFields = (clb: CollaboratorIdType) => {
|
||||
return {
|
||||
...(clb.tmbId && { tmbId: clb.tmbId }),
|
||||
...(clb.groupId && { groupId: clb.groupId }),
|
||||
...(clb.orgId && { orgId: clb.orgId })
|
||||
};
|
||||
};
|
||||
|
|
@ -8,7 +8,7 @@ import {
|
|||
import { MongoTeamMember } from './teamMemberSchema';
|
||||
import { MongoTeam } from './teamSchema';
|
||||
import { type UpdateTeamProps } from '@fastgpt/global/support/user/team/controller';
|
||||
import { getResourcePermission } from '../../permission/controller';
|
||||
import { getTmbPermission } from '../../permission/controller';
|
||||
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
|
||||
import { TeamDefaultRoleVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
|
|
@ -26,7 +26,7 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemTyp
|
|||
}
|
||||
|
||||
const role =
|
||||
(await getResourcePermission({
|
||||
(await getTmbPermission({
|
||||
resourceType: PerResourceTypeEnum.team,
|
||||
teamId: tmb.teamId,
|
||||
tmbId: tmb._id
|
||||
|
|
|
|||
|
|
@ -274,7 +274,11 @@ const MySelect = <T = any,>(
|
|||
w={selectItem.iconSize ?? '1rem'}
|
||||
/>
|
||||
)}
|
||||
{selectItem?.alias || selectItem?.label || placeholder}
|
||||
{
|
||||
<Box noOfLines={1}>
|
||||
{selectItem?.alias || selectItem?.label || placeholder}
|
||||
</Box>
|
||||
}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Box, Flex, useTheme, Grid, type GridProps, HStack } from '@chakra-ui/react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { Box, Flex, Grid, type GridProps, HStack } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import QuestionTip from '../MyTooltip/QuestionTip';
|
||||
|
||||
|
|
@ -16,57 +16,83 @@ type Props<T> = Omit<GridProps, 'onChange'> & {
|
|||
defaultBg?: string;
|
||||
activeBg?: string;
|
||||
onChange: (e: T) => void;
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
const LeftRadio = <T = any,>({
|
||||
list,
|
||||
value,
|
||||
align = 'flex-top',
|
||||
align = 'center',
|
||||
px = 3.5,
|
||||
py = 4,
|
||||
defaultBg = 'myGray.50',
|
||||
activeBg = 'primary.50',
|
||||
onChange,
|
||||
isDisabled = false,
|
||||
...props
|
||||
}: Props<T>) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
const getBoxStyle = useCallback(
|
||||
(isActive: boolean) => {
|
||||
const baseStyle = {
|
||||
px,
|
||||
py,
|
||||
border: 'base',
|
||||
borderWidth: '1px',
|
||||
borderRadius: 'md'
|
||||
};
|
||||
|
||||
if (isActive) {
|
||||
return {
|
||||
...baseStyle,
|
||||
borderColor: 'primary.400',
|
||||
bg: activeBg,
|
||||
boxShadow: 'focus',
|
||||
cursor: 'pointer',
|
||||
opacity: 1
|
||||
};
|
||||
}
|
||||
if (isDisabled) {
|
||||
return {
|
||||
...baseStyle,
|
||||
bg: 'myWhite.300',
|
||||
borderColor: 'myGray.200',
|
||||
color: 'myGray.500',
|
||||
cursor: 'not-allowed',
|
||||
opacity: 0.6
|
||||
};
|
||||
}
|
||||
return {
|
||||
...baseStyle,
|
||||
bg: defaultBg,
|
||||
_hover: { borderColor: 'primary.300' },
|
||||
cursor: 'pointer',
|
||||
opacity: 1
|
||||
};
|
||||
},
|
||||
[activeBg, defaultBg, isDisabled, px, py]
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid gridGap={[3, 5]} fontSize={['sm', 'md']} {...props}>
|
||||
{list.map((item) => (
|
||||
<Box
|
||||
key={item.value as any}
|
||||
cursor={'pointer'}
|
||||
userSelect={'none'}
|
||||
px={px}
|
||||
py={py}
|
||||
border={'base'}
|
||||
borderWidth={'1px'}
|
||||
borderRadius={'md'}
|
||||
position={'relative'}
|
||||
{...(value === item.value
|
||||
? {
|
||||
borderColor: list.length > 1 ? 'primary.400' : '',
|
||||
bg: activeBg,
|
||||
boxShadow: list.length > 1 ? 'focus' : 'none'
|
||||
}
|
||||
: {
|
||||
bg: defaultBg,
|
||||
_hover: {
|
||||
borderColor: 'primary.300'
|
||||
}
|
||||
})}
|
||||
onClick={() => onChange(item.value)}
|
||||
>
|
||||
{/* Circle */}
|
||||
<Flex alignItems={'center'}>
|
||||
{list.length > 1 && (
|
||||
{list.map((item) => {
|
||||
const isActive = value === item.value;
|
||||
return (
|
||||
<Box
|
||||
key={item.value as any}
|
||||
position={'relative'}
|
||||
userSelect={'none'}
|
||||
onClick={() => !isDisabled && onChange(item.value)}
|
||||
{...getBoxStyle(isActive)}
|
||||
>
|
||||
<Flex alignItems={align}>
|
||||
{/* Circle */}
|
||||
<Box
|
||||
w={'18px'}
|
||||
h={'18px'}
|
||||
borderWidth={'2.4px'}
|
||||
borderColor={value === item.value ? 'primary.015' : 'transparent'}
|
||||
borderColor={isActive ? 'primary.015' : 'transparent'}
|
||||
borderRadius={'50%'}
|
||||
mr={3}
|
||||
>
|
||||
|
|
@ -74,52 +100,59 @@ const LeftRadio = <T = any,>({
|
|||
w={'100%'}
|
||||
h={'100%'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={value === item.value ? 'primary.600' : 'borderColor.high'}
|
||||
bg={value === item.value ? 'primary.1' : 'transparent'}
|
||||
borderRadius={'50%'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
{...(isActive
|
||||
? {
|
||||
borderColor: 'primary.600',
|
||||
bg: 'primary.1'
|
||||
}
|
||||
: {
|
||||
borderColor: 'borderColor.high',
|
||||
bg: 'transparent'
|
||||
})}
|
||||
>
|
||||
<Box
|
||||
w={'5px'}
|
||||
h={'5px'}
|
||||
borderRadius={'50%'}
|
||||
bg={value === item.value ? 'primary.600' : 'transparent'}
|
||||
bg={isActive ? 'primary.600' : 'transparent'}
|
||||
></Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
<Box flex={'1 0 0'}>
|
||||
{typeof item.title === 'string' ? (
|
||||
<HStack
|
||||
spacing={1}
|
||||
color={'myGray.900'}
|
||||
fontWeight={item.desc ? '500' : 'normal'}
|
||||
whiteSpace={'nowrap'}
|
||||
fontSize={'sm'}
|
||||
lineHeight={1}
|
||||
>
|
||||
<Box>{t(item.title as any)}</Box>
|
||||
{!!item.tooltip && <QuestionTip label={item.tooltip} color={'myGray.600'} />}
|
||||
</HStack>
|
||||
) : (
|
||||
item.title
|
||||
)}
|
||||
<Box flex={'1 0 0'}>
|
||||
{typeof item.title === 'string' ? (
|
||||
<HStack
|
||||
spacing={1}
|
||||
fontWeight={item.desc ? 'medium' : 'normal'}
|
||||
whiteSpace={'nowrap'}
|
||||
fontSize={'sm'}
|
||||
lineHeight={1}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
<Box>{t(item.title as any)}</Box>
|
||||
{!!item.tooltip && <QuestionTip label={item.tooltip} color={'myGray.600'} />}
|
||||
</HStack>
|
||||
) : (
|
||||
item.title
|
||||
)}
|
||||
|
||||
{!!item.desc && (
|
||||
<Box fontSize={'xs'} color={'myGray.500'} mt={1.5} lineHeight={1.2}>
|
||||
{t(item.desc as any)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
{item?.children && (
|
||||
<Box mt={4} pt={4} borderTop={'base'} cursor={'default'}>
|
||||
{item?.children}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
{!!item.desc && (
|
||||
<Box fontSize={'xs'} mt={1.5} lineHeight={1.2}>
|
||||
{t(item.desc as any)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
{item?.children && (
|
||||
<Box mt={4} pt={4} borderTop={'base'} cursor={'default'}>
|
||||
{item?.children}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
|
|||
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
|
||||
import { ListPlugin } from '@lexical/react/LexicalListPlugin';
|
||||
import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin';
|
||||
import { TabIndentationPlugin } from '@lexical/react/LexicalTabIndentationPlugin';
|
||||
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
|
||||
import { HeadingNode, QuoteNode } from '@lexical/rich-text';
|
||||
import { ListItemNode, ListNode } from '@lexical/list';
|
||||
|
|
@ -219,7 +220,6 @@ export default function Editor({
|
|||
)}
|
||||
{variableLabels.length > 0 && <VariablePickerPlugin variables={variables} />}
|
||||
<OnBlurPlugin onBlur={onBlur} />
|
||||
<ListDisplayFixPlugin />
|
||||
<OnChangePlugin
|
||||
onChange={(editorState, editor) => {
|
||||
const rootElement = editor.getRootElement();
|
||||
|
|
@ -232,11 +232,12 @@ export default function Editor({
|
|||
|
||||
{isRichText && (
|
||||
<>
|
||||
{/* <ListPlugin />
|
||||
<ListDisplayFixPlugin />
|
||||
<TabIndentationPlugin />
|
||||
<ListPlugin />
|
||||
<CheckListPlugin />
|
||||
<ListExitPlugin /> */}
|
||||
<TabToSpacesPlugin />
|
||||
{/* <MarkdownPlugin /> */}
|
||||
<ListExitPlugin />
|
||||
<MarkdownPlugin />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ export default function ListExitPlugin(): JSX.Element | null {
|
|||
}
|
||||
|
||||
const anchorNode = selection.anchor.getNode();
|
||||
const listItemNode = anchorNode.getParent();
|
||||
const listItemNode = $isListItemNode(anchorNode) ? anchorNode : anchorNode.getParent();
|
||||
|
||||
if ($isListItemNode(listItemNode)) {
|
||||
// Check if cursor is at the beginning of an empty list item
|
||||
|
|
|
|||
|
|
@ -23,3 +23,88 @@ export type EditorVariableLabelPickerType = {
|
|||
};
|
||||
|
||||
export type FormPropsType = Omit<BoxProps, 'onChange' | 'onBlur'>;
|
||||
|
||||
// Lexical editor node types
|
||||
export type BaseEditorNode = {
|
||||
type: string;
|
||||
version: number;
|
||||
};
|
||||
|
||||
export type TextEditorNode = BaseEditorNode & {
|
||||
type: 'text';
|
||||
text: string;
|
||||
detail: number;
|
||||
format: number;
|
||||
mode: string;
|
||||
style: string;
|
||||
};
|
||||
|
||||
export type LineBreakEditorNode = BaseEditorNode & {
|
||||
type: 'linebreak';
|
||||
};
|
||||
|
||||
export type VariableLabelEditorNode = BaseEditorNode & {
|
||||
type: 'variableLabel';
|
||||
variableKey: string;
|
||||
};
|
||||
|
||||
export type VariableEditorNode = BaseEditorNode & {
|
||||
type: 'Variable';
|
||||
variableKey: string;
|
||||
};
|
||||
|
||||
export type TabEditorNode = BaseEditorNode & {
|
||||
type: 'tab';
|
||||
};
|
||||
|
||||
export type ChildEditorNode =
|
||||
| TextEditorNode
|
||||
| LineBreakEditorNode
|
||||
| VariableLabelEditorNode
|
||||
| VariableEditorNode
|
||||
| TabEditorNode;
|
||||
|
||||
export type ParagraphEditorNode = BaseEditorNode & {
|
||||
type: 'paragraph';
|
||||
children: ChildEditorNode[];
|
||||
direction: string;
|
||||
format: string;
|
||||
indent: number;
|
||||
};
|
||||
|
||||
export type ListItemEditorNode = BaseEditorNode & {
|
||||
type: 'listitem';
|
||||
children: Array<ChildEditorNode | ListEditorNode>;
|
||||
direction: string | null;
|
||||
format: string;
|
||||
indent: number;
|
||||
value: number;
|
||||
};
|
||||
|
||||
export type ListEditorNode = BaseEditorNode & {
|
||||
type: 'list';
|
||||
children: ListItemEditorNode[];
|
||||
direction: string | null;
|
||||
format: string;
|
||||
indent: number;
|
||||
listType: 'bullet' | 'number';
|
||||
start: number;
|
||||
tag: 'ul' | 'ol';
|
||||
};
|
||||
|
||||
export type EditorState = {
|
||||
root: {
|
||||
type: 'root';
|
||||
children: Array<ParagraphEditorNode | ListEditorNode>;
|
||||
direction: string;
|
||||
format: string;
|
||||
indent: number;
|
||||
} & BaseEditorNode;
|
||||
};
|
||||
|
||||
export type ListItemInfo = {
|
||||
type: 'bullet' | 'number';
|
||||
text: string;
|
||||
indent: number;
|
||||
numberValue?: number;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,12 +6,19 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import type { DecoratorNode, Klass, LexicalEditor, LexicalNode } from 'lexical';
|
||||
import type { Klass, LexicalEditor, LexicalNode } from 'lexical';
|
||||
import type { EntityMatch } from '@lexical/text';
|
||||
import { $createTextNode, $isTextNode, TextNode } from 'lexical';
|
||||
import { useCallback } from 'react';
|
||||
import type { VariableLabelNode } from './plugins/VariableLabelPlugin/node';
|
||||
import type { VariableNode } from './plugins/VariablePlugin/node';
|
||||
import type {
|
||||
ListItemEditorNode,
|
||||
ListEditorNode,
|
||||
ParagraphEditorNode,
|
||||
EditorState,
|
||||
ListItemInfo
|
||||
} from './type';
|
||||
|
||||
export function registerLexicalTextEntity<T extends TextNode | VariableLabelNode | VariableNode>(
|
||||
editor: LexicalEditor,
|
||||
|
|
@ -175,31 +182,148 @@ export function registerLexicalTextEntity<T extends TextNode | VariableLabelNode
|
|||
return [removePlainTextTransform, removeReverseNodeTransform];
|
||||
}
|
||||
|
||||
export function textToEditorState(text = '') {
|
||||
const paragraph = typeof text === 'string' ? text?.split('\n') : [''];
|
||||
// text to editor state
|
||||
const parseTextLine = (line: string) => {
|
||||
const trimmed = line.trimStart();
|
||||
const indentLevel = Math.floor((line.length - trimmed.length) / 2);
|
||||
|
||||
const bulletMatch = trimmed.match(/^- (.*)$/);
|
||||
if (bulletMatch) {
|
||||
return { type: 'bullet', text: bulletMatch[1], indent: indentLevel };
|
||||
}
|
||||
|
||||
const numberMatch = trimmed.match(/^(\d+)\. (.*)$/);
|
||||
if (numberMatch) {
|
||||
return {
|
||||
type: 'number',
|
||||
text: numberMatch[2],
|
||||
indent: indentLevel,
|
||||
numberValue: parseInt(numberMatch[1])
|
||||
};
|
||||
}
|
||||
|
||||
return { type: 'paragraph', text: trimmed, indent: indentLevel };
|
||||
};
|
||||
|
||||
const buildListStructure = (items: ListItemInfo[]) => {
|
||||
const result: ListEditorNode[] = [];
|
||||
|
||||
let i = 0;
|
||||
while (i < items.length) {
|
||||
const currentListType = items[i].type;
|
||||
const currentIndent = items[i].indent;
|
||||
const currentListItems: ListItemEditorNode[] = [];
|
||||
|
||||
// Collect consecutive items of the same type
|
||||
while (i < items.length && items[i].type === currentListType) {
|
||||
const listItem: ListItemEditorNode = {
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: items[i].text,
|
||||
type: 'text' as const,
|
||||
version: 1
|
||||
}
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem' as const,
|
||||
version: 1,
|
||||
value: items[i].numberValue || 1
|
||||
};
|
||||
|
||||
// Collect nested items
|
||||
const nestedItems: ListItemInfo[] = [];
|
||||
let j = i + 1;
|
||||
while (j < items.length && items[j].indent > currentIndent) {
|
||||
nestedItems.push(items[j]);
|
||||
j++;
|
||||
}
|
||||
|
||||
// recursively build nested lists and add them to the current item's children
|
||||
if (nestedItems.length > 0) {
|
||||
const nestedLists = buildListStructure(nestedItems);
|
||||
listItem.children.push(...nestedLists);
|
||||
}
|
||||
|
||||
currentListItems.push(listItem);
|
||||
i = j;
|
||||
}
|
||||
|
||||
result.push({
|
||||
children: currentListItems,
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'list' as const,
|
||||
version: 1,
|
||||
listType: currentListType,
|
||||
start: 1,
|
||||
tag: currentListType === 'bullet' ? 'ul' : ('ol' as const)
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const textToEditorState = (text = '') => {
|
||||
const lines = text.split('\n');
|
||||
const children: Array<ParagraphEditorNode | ListEditorNode> = [];
|
||||
|
||||
let i = 0;
|
||||
while (i < lines.length) {
|
||||
const parsed = parseTextLine(lines[i]);
|
||||
|
||||
if (parsed.type === 'paragraph') {
|
||||
children.push({
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: parsed.text,
|
||||
type: 'text',
|
||||
version: 1
|
||||
}
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: parsed.indent,
|
||||
type: 'paragraph',
|
||||
version: 1
|
||||
});
|
||||
i++;
|
||||
} else {
|
||||
const listItems: ListItemInfo[] = [];
|
||||
|
||||
while (i < lines.length) {
|
||||
const currentParsed = parseTextLine(lines[i]);
|
||||
if (currentParsed.type === 'paragraph') {
|
||||
break;
|
||||
}
|
||||
listItems.push({
|
||||
type: currentParsed.type as 'bullet' | 'number',
|
||||
text: currentParsed.text,
|
||||
indent: currentParsed.indent,
|
||||
numberValue: currentParsed.numberValue
|
||||
});
|
||||
i++;
|
||||
}
|
||||
|
||||
// build nested lists and add to children
|
||||
const lists = buildListStructure(listItems) as ListEditorNode[];
|
||||
children.push(...lists);
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
root: {
|
||||
children: paragraph.map((p) => {
|
||||
return {
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: p,
|
||||
type: 'text',
|
||||
version: 1
|
||||
}
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1
|
||||
};
|
||||
}),
|
||||
children: children,
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
|
|
@ -207,30 +331,9 @@ export function textToEditorState(text = '') {
|
|||
version: 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const varRegex = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;
|
||||
export const getVars = (value: string) => {
|
||||
if (!value) return [];
|
||||
const keys =
|
||||
value
|
||||
.match(varRegex)
|
||||
?.map((item) => {
|
||||
return item.replace('{{', '').replace('}}', '');
|
||||
})
|
||||
.filter((key) => key.length <= 10) || [];
|
||||
const keyObj: Record<string, boolean> = {};
|
||||
// remove duplicate keys
|
||||
const res: string[] = [];
|
||||
keys.forEach((key) => {
|
||||
if (keyObj[key]) return;
|
||||
|
||||
keyObj[key] = true;
|
||||
res.push(key);
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
// menu text match
|
||||
export type MenuTextMatch = {
|
||||
leadOffset: number;
|
||||
matchingString: string;
|
||||
|
|
@ -266,22 +369,102 @@ export function useBasicTypeaheadTriggerMatch(
|
|||
);
|
||||
}
|
||||
|
||||
export function editorStateToText(editor: LexicalEditor) {
|
||||
const editorStateTextString: string[] = [];
|
||||
const paragraphs = editor.getEditorState().toJSON().root.children;
|
||||
paragraphs.forEach((paragraph: any) => {
|
||||
const children = paragraph.children || [];
|
||||
const paragraphText: string[] = [];
|
||||
children.forEach((child: any) => {
|
||||
if (child.type === 'linebreak') {
|
||||
paragraphText.push('\n');
|
||||
} else if (child.text) {
|
||||
paragraphText.push(child.text);
|
||||
} else if (child.type === 'variableLabel' || child.type === 'Variable') {
|
||||
paragraphText.push(child.variableKey);
|
||||
}
|
||||
// editor state to text
|
||||
const processListItem = ({
|
||||
listItem,
|
||||
listType,
|
||||
index,
|
||||
indentLevel
|
||||
}: {
|
||||
listItem: ListItemEditorNode;
|
||||
listType: 'bullet' | 'number';
|
||||
index: number;
|
||||
indentLevel: number;
|
||||
}) => {
|
||||
const results = [];
|
||||
|
||||
const itemText: string[] = [];
|
||||
const nestedLists: ListEditorNode[] = [];
|
||||
|
||||
// Separate text and nested lists
|
||||
listItem.children.forEach((child) => {
|
||||
if (child.type === 'linebreak') {
|
||||
itemText.push('\n');
|
||||
} else if (child.type === 'text') {
|
||||
itemText.push(child.text);
|
||||
} else if (child.type === 'tab') {
|
||||
itemText.push(' ');
|
||||
} else if (child.type === 'variableLabel' || child.type === 'Variable') {
|
||||
itemText.push(child.variableKey);
|
||||
} else if (child.type === 'list') {
|
||||
nestedLists.push(child);
|
||||
}
|
||||
});
|
||||
|
||||
// Add prefix and indent
|
||||
const itemTextString = itemText.join('').trim();
|
||||
const indent = ' '.repeat(indentLevel);
|
||||
const prefix = listType === 'bullet' ? '- ' : `${index + 1}. `;
|
||||
results.push(indent + prefix + itemTextString);
|
||||
|
||||
// Handle nested lists
|
||||
nestedLists.forEach((nestedList) => {
|
||||
const nestedResults = processList({
|
||||
list: nestedList,
|
||||
indentLevel: indentLevel + 1
|
||||
});
|
||||
editorStateTextString.push(paragraphText.join(''));
|
||||
results.push(...nestedResults);
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
const processList = ({ list, indentLevel = 0 }: { list: ListEditorNode; indentLevel?: number }) => {
|
||||
const results: string[] = [];
|
||||
|
||||
list.children.forEach((listItem, index: number) => {
|
||||
if (listItem.type === 'listitem') {
|
||||
const itemResults = processListItem({
|
||||
listItem,
|
||||
listType: list.listType,
|
||||
index,
|
||||
indentLevel
|
||||
});
|
||||
results.push(...itemResults);
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
export const editorStateToText = (editor: LexicalEditor) => {
|
||||
const editorStateTextString: string[] = [];
|
||||
const editorState = editor.getEditorState().toJSON() as EditorState;
|
||||
const paragraphs = editorState.root.children;
|
||||
|
||||
paragraphs.forEach((paragraph) => {
|
||||
if (paragraph.type === 'list') {
|
||||
const listResults = processList({ list: paragraph });
|
||||
editorStateTextString.push(...listResults);
|
||||
} else if (paragraph.type === 'paragraph') {
|
||||
const children = paragraph.children;
|
||||
const paragraphText: string[] = [];
|
||||
|
||||
const indentSpaces = ' '.repeat(paragraph.indent || 0);
|
||||
|
||||
children.forEach((child) => {
|
||||
if (child.type === 'linebreak') {
|
||||
paragraphText.push('\n');
|
||||
} else if (child.type === 'text') {
|
||||
paragraphText.push(child.text);
|
||||
} else if (child.type === 'tab') {
|
||||
paragraphText.push(' ');
|
||||
} else if (child.type === 'variableLabel' || child.type === 'Variable') {
|
||||
paragraphText.push(child.variableKey);
|
||||
}
|
||||
});
|
||||
|
||||
const finalText = paragraphText.join('');
|
||||
editorStateTextString.push(indentSpaces + finalText);
|
||||
}
|
||||
});
|
||||
return editorStateTextString.join('\n');
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
import { type PaginationProps, type PaginationResponse } from '../common/fetch/type';
|
||||
import MyMenu from '../components/common/MyMenu';
|
||||
import { useSystem } from './useSystem';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const thresholdVal = 200;
|
||||
|
||||
|
|
@ -35,19 +36,18 @@ export function usePagination<DataT, ResT = {}>(
|
|||
defaultPageSize = 10,
|
||||
pageSizeOptions: defaultPageSizeOptions,
|
||||
params,
|
||||
defaultRequest = true,
|
||||
type = 'button',
|
||||
onChange,
|
||||
refreshDeps,
|
||||
scrollLoadType = 'bottom',
|
||||
EmptyTip,
|
||||
pollingInterval,
|
||||
pollingWhenHidden = false
|
||||
pollingWhenHidden = false,
|
||||
storeToQuery = false
|
||||
}: {
|
||||
defaultPageSize?: number;
|
||||
pageSizeOptions?: number[];
|
||||
params?: DataT;
|
||||
defaultRequest?: boolean;
|
||||
type?: 'button' | 'scroll';
|
||||
onChange?: (pageNum: number) => void;
|
||||
refreshDeps?: any[];
|
||||
|
|
@ -56,15 +56,20 @@ export function usePagination<DataT, ResT = {}>(
|
|||
EmptyTip?: React.JSX.Element;
|
||||
pollingInterval?: number;
|
||||
pollingWhenHidden?: boolean;
|
||||
storeToQuery?: boolean;
|
||||
}
|
||||
) {
|
||||
const router = useRouter();
|
||||
let { page = '1' } = router.query as { page: string };
|
||||
const numPage = Number(page);
|
||||
|
||||
const { toast } = useToast();
|
||||
const { isPc } = useSystem();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isLoading, { setTrue, setFalse }] = useBoolean(false);
|
||||
|
||||
const [pageNum, setPageNum] = useState(1);
|
||||
const [pageNum, setPageNum] = useState(numPage);
|
||||
const [pageSize, setPageSize] = useState(defaultPageSize);
|
||||
const pageSizeOptions = useCreation(
|
||||
() => defaultPageSizeOptions || [10, 20, 50, 100],
|
||||
|
|
@ -76,7 +81,7 @@ export function usePagination<DataT, ResT = {}>(
|
|||
const totalDataLength = useMemo(() => Math.max(total, data.length), [total, data.length]);
|
||||
|
||||
const isEmpty = total === 0 && !isLoading;
|
||||
const noMore = data.length >= totalDataLength;
|
||||
const noMore = data.length > 0 && data.length >= totalDataLength;
|
||||
|
||||
const fetchData = useMemoizedFn(
|
||||
async (num: number = pageNum, ScrollContainerRef?: RefObject<HTMLDivElement>) => {
|
||||
|
|
@ -92,6 +97,16 @@ export function usePagination<DataT, ResT = {}>(
|
|||
});
|
||||
|
||||
setPageNum(num);
|
||||
if (storeToQuery && num !== pageNum) {
|
||||
router.replace({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
page: num
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.total !== undefined && setTotal(res.total);
|
||||
|
||||
if (type === 'scroll') {
|
||||
|
|
@ -268,7 +283,8 @@ export function usePagination<DataT, ResT = {}>(
|
|||
// Watch scroll position
|
||||
useThrottleEffect(
|
||||
() => {
|
||||
if (!ref?.current || type !== 'scroll' || noMore || isLoading) return;
|
||||
if (!ref?.current || type !== 'scroll' || noMore || isLoading || data.length === 0)
|
||||
return;
|
||||
const { scrollTop, scrollHeight, clientHeight } = ref.current;
|
||||
|
||||
if (
|
||||
|
|
@ -313,9 +329,16 @@ export function usePagination<DataT, ResT = {}>(
|
|||
);
|
||||
|
||||
// Reload data
|
||||
const isFirstLoad = useRef(true);
|
||||
const { runAsync: refresh } = useRequest(
|
||||
async () => {
|
||||
defaultRequest && fetchData(1);
|
||||
if (isFirstLoad.current) {
|
||||
isFirstLoad.current = false;
|
||||
fetchData(numPage);
|
||||
return;
|
||||
}
|
||||
|
||||
fetchData(1);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
|
|
@ -323,6 +346,7 @@ export function usePagination<DataT, ResT = {}>(
|
|||
throttleWait: 100
|
||||
}
|
||||
);
|
||||
// Page size refresh
|
||||
useEffect(() => {
|
||||
data.length > 0 && fetchData();
|
||||
}, [pageSize]);
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
"forbid_hint": "After forbidden, this invitation link will become invalid. This action is irreversible. Are you sure you want to deactivate?",
|
||||
"forbid_success": "Forbid success",
|
||||
"forbidden": "Forbidden",
|
||||
"link_forbidden": "Forbidden",
|
||||
"forbidden_tip": "Confirm disabling {{username}}? The member will be marked as 'disabled' and will not be able to log in. Operation data will not be deleted, and resources under the account will be automatically transferred to the team owner.",
|
||||
"group": "group",
|
||||
"group_name": "Group name",
|
||||
"handle_invitation": "Handle Invitation",
|
||||
|
|
@ -113,6 +113,7 @@
|
|||
"label_sync": "Tag sync",
|
||||
"leave": "Resigned",
|
||||
"leave_team_failed": "Leaving the team exception",
|
||||
"link_forbidden": "Forbidden",
|
||||
"log_admin_add_plan": "【{{name}}】A package will be added to a team with a team id [{{teamId}}]",
|
||||
"log_admin_add_user": "【{{name}}】Create a user named [{{userName}}]",
|
||||
"log_admin_change_license": "【{{name}}】Changed License",
|
||||
|
|
@ -196,6 +197,7 @@
|
|||
"log_user": "Operator",
|
||||
"login": "Log in",
|
||||
"manage_member": "Managing members",
|
||||
"manage_per": "Administrative permissions",
|
||||
"member": "member",
|
||||
"member_group": "Belonging to member group",
|
||||
"move_app": "App location movement",
|
||||
|
|
@ -222,7 +224,6 @@
|
|||
"relocate_department": "Department Mobile",
|
||||
"remark": "remark",
|
||||
"remove_tip": "Confirm to remove {{username}} from the team? The member will be marked as 'leave'. Operation data will not be deleted, and resources under the account will be automatically transferred to the team owner.",
|
||||
"forbidden_tip": "Confirm disabling {{username}}? The member will be marked as 'disabled' and will not be able to log in. Operation data will not be deleted, and resources under the account will be automatically transferred to the team owner.",
|
||||
"restore_tip": "Confirm to join the team {{username}}? \nOnly the availability and related permissions of this member account are restored, and the resources under the account cannot be restored.",
|
||||
"restore_tip_title": "Recovery confirmation",
|
||||
"retain_admin_permissions": "Keep administrator rights",
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@
|
|||
"module.type": "\"{{type}}\" type\n{{description}}",
|
||||
"modules.Title is required": "Module name cannot be empty",
|
||||
"month.unit": "Day",
|
||||
"move.hint": "After moving, the selected application/folder will inherit the permission settings of the new folder, and the original permission settings will become invalid.",
|
||||
"move.hint": "After moving, the selected app/folder will inherit the permission settings for the new folder.",
|
||||
"move_app": "Move Application",
|
||||
"no_mcp_tools_list": "No data yet, the MCP address needs to be parsed first",
|
||||
"node_not_intro": "This node is not introduced",
|
||||
|
|
@ -190,7 +190,7 @@
|
|||
"pdf_enhance_parse": "PDF enhancement analysis",
|
||||
"pdf_enhance_parse_price": "{{price}}Points/page",
|
||||
"pdf_enhance_parse_tips": "Calling PDF recognition model for parsing, you can convert it into Markdown and retain pictures in the document. At the same time, you can also identify scanned documents, which will take a long time to identify them.",
|
||||
"permission.des.manage": "Based on write permissions, you can configure publishing channels, view conversation logs, and assign permissions to the application.",
|
||||
"permission.des.manage": "Can configure publishing channels, view logs, and assign application permissions",
|
||||
"permission.des.read": "Use the app to have conversations",
|
||||
"permission.des.readChatLog": "Can view chat logs",
|
||||
"permission.des.write": "Can view and edit apps",
|
||||
|
|
|
|||
|
|
@ -966,7 +966,7 @@
|
|||
"permission.Private Tip": "Only Available to Yourself",
|
||||
"permission.Public": "Team",
|
||||
"permission.Public Tip": "Available to All Team Members",
|
||||
"permission.Remove InheritPermission Confirm": "This operation will invalidate permission inheritance. Proceed?",
|
||||
"permission.Remove InheritPermission Confirm": "This modification conflicts with inheritance permissions, which will cause permission inheritance to be invalid. Will it be carried out?",
|
||||
"permission.Resume InheritPermission Confirm": "Resume inheriting permissions from the parent folder?",
|
||||
"permission.Resume InheritPermission Failed": "Resume Failed",
|
||||
"permission.Resume InheritPermission Success": "Resume Successful",
|
||||
|
|
@ -976,6 +976,7 @@
|
|||
"permission.change_owner_success": "Ownership Transferred Successfully",
|
||||
"permission.change_owner_tip": "Your permissions will not be retained after the transfer",
|
||||
"permission.change_owner_to": "Transfer to",
|
||||
"permission.common_member": "Common members",
|
||||
"permission.manager": "administrator",
|
||||
"permission.read": "Read permission",
|
||||
"permission.write": "write permission",
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@
|
|||
"llm_paragraph_mode_force_desc": "Force the use of the model to automatically identify paragraphs and ignore paragraphs in the original text (if any)",
|
||||
"loading": "Loading...",
|
||||
"max_chunk_size": "Maximum chunk size",
|
||||
"move.hint": "After moving, the selected knowledge base/folder will inherit the permission settings of the new folder, and the original permission settings will become invalid.",
|
||||
"move.hint": "After moving, the selected knowledge base/folder will inherit the permission settings for the new folder.",
|
||||
"noChildren": "No subdirectories",
|
||||
"noSelectedFolder": "No selected folder",
|
||||
"noSelectedId": "No selected ID",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
"delete.admin_success": "Admin Deleted Successfully",
|
||||
"delete.failed": "Delete failed",
|
||||
"delete.success": "Delete successfully",
|
||||
"has_chosen": "Selected",
|
||||
"login.Dingtalk": "DingTalk Login",
|
||||
"login.error": "Login Error",
|
||||
"login.password_condition": "Password can be up to 60 characters",
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@
|
|||
"forbid_hint": "停用后,该邀请链接将失效。 该操作不可撤销,是否确认停用?",
|
||||
"forbid_success": "停用成功",
|
||||
"forbidden": "停用",
|
||||
"link_forbidden": "禁用",
|
||||
"forbidden_tip": "确认将 {{username}} 禁用?成员将被标记为“禁用”并无法登录,不删除操作数据,账号下资源自动转让给团队所有者。",
|
||||
"group": "群组",
|
||||
"group_name": "群组名称",
|
||||
"handle_invitation": "处理团队邀请",
|
||||
|
|
@ -115,6 +115,7 @@
|
|||
"label_sync": "标签同步",
|
||||
"leave": "离开",
|
||||
"leave_team_failed": "离开团队异常",
|
||||
"link_forbidden": "禁用",
|
||||
"log_admin_add_plan": "【{{name}}】将给团队id为【{{teamId}}】的团队添加了套餐",
|
||||
"log_admin_add_user": "【{{name}}】创建了一个名为【{{userName}}】的用户",
|
||||
"log_admin_change_license": "【{{name}}】变更了License",
|
||||
|
|
@ -200,6 +201,7 @@
|
|||
"log_user": "操作人员",
|
||||
"login": "登录",
|
||||
"manage_member": "管理成员",
|
||||
"manage_per": "管理权限",
|
||||
"member": "成员",
|
||||
"member_group": "所属群组",
|
||||
"move_app": "应用位置移动",
|
||||
|
|
@ -226,7 +228,6 @@
|
|||
"relocate_department": "部门移动",
|
||||
"remark": "备注",
|
||||
"remove_tip": "确认将 {{username}} 移出团队?成员将被标记为“离开”,不删除操作数据,账号下资源自动转让给团队所有者。",
|
||||
"forbidden_tip": "确认将 {{username}} 禁用?成员将被标记为“禁用”并无法登录,不删除操作数据,账号下资源自动转让给团队所有者。",
|
||||
"restore_tip": "确认将 {{username}} 加入团队吗?仅恢复该成员账号可用性及相关权限,无法恢复账号下资源。",
|
||||
"restore_tip_title": "恢复确认",
|
||||
"retain_admin_permissions": "保留管理员权限",
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@
|
|||
"module.type": "\"{{type}}\"类型\n{{description}}",
|
||||
"modules.Title is required": "模块名不能为空",
|
||||
"month.unit": "号",
|
||||
"move.hint": "移动后,所选应用/文件夹将继承新文件夹的权限设置,原先的权限设置失效。",
|
||||
"move.hint": "移动后,所选应用/文件夹将继承新文件夹的权限设置。",
|
||||
"move_app": "移动应用",
|
||||
"no_mcp_tools_list": "暂无数据,需先解析 MCP 地址",
|
||||
"node_not_intro": "这个节点没有介绍",
|
||||
|
|
@ -199,7 +199,7 @@
|
|||
"pdf_enhance_parse": "PDF增强解析",
|
||||
"pdf_enhance_parse_price": "{{price}}积分/页",
|
||||
"pdf_enhance_parse_tips": "调用 PDF 识别模型进行解析,可以将其转换成 Markdown 并保留文档中的图片,同时也可以对扫描件进行识别,识别时间较长。",
|
||||
"permission.des.manage": "写权限基础上,可配置发布渠道、查看对话日志、分配该应用权限",
|
||||
"permission.des.manage": "可配置发布渠道、查看日志、分配应用权限",
|
||||
"permission.des.read": "可使用该应用进行对话",
|
||||
"permission.des.readChatLog": "可查看对话日志",
|
||||
"permission.des.write": "可查看和编辑应用",
|
||||
|
|
|
|||
|
|
@ -967,7 +967,7 @@
|
|||
"permission.Private Tip": "仅自己可用",
|
||||
"permission.Public": "协作",
|
||||
"permission.Public Tip": "团队所有成员可使用",
|
||||
"permission.Remove InheritPermission Confirm": "此操作会导致权限继承失效,是否进行?",
|
||||
"permission.Remove InheritPermission Confirm": "此修改与继承权限存在冲突,会导致权限继承失效,是否进行?",
|
||||
"permission.Resume InheritPermission Confirm": "是否恢复为继承父级文件夹的权限?",
|
||||
"permission.Resume InheritPermission Failed": "恢复失败",
|
||||
"permission.Resume InheritPermission Success": "恢复成功",
|
||||
|
|
@ -977,6 +977,7 @@
|
|||
"permission.change_owner_success": "成功转移所有权",
|
||||
"permission.change_owner_tip": "转移后您的权限不会保留",
|
||||
"permission.change_owner_to": "转移给",
|
||||
"permission.common_member": "普通成员",
|
||||
"permission.manager": "管理员",
|
||||
"permission.read": "读权限",
|
||||
"permission.write": "写权限",
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@
|
|||
"llm_paragraph_mode_force_desc": "强制使用模型自动识别段落,并忽略原文本的段落(如有)",
|
||||
"loading": "加载中...",
|
||||
"max_chunk_size": "最大分块大小",
|
||||
"move.hint": "移动后,所选知识库/文件夹将继承新文件夹的权限设置,原先的权限设置失效。",
|
||||
"move.hint": "移动后,所选知识库/文件夹将继承新文件夹的权限设置。",
|
||||
"noChildren": "无子目录",
|
||||
"noSelectedFolder": "没有选择文件夹",
|
||||
"noSelectedId": "没有选择 ID",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
"delete.admin_success": "删除管理员成功",
|
||||
"delete.failed": "删除失败",
|
||||
"delete.success": "删除成功",
|
||||
"has_chosen": "已选择",
|
||||
"login.Dingtalk": "钉钉登录",
|
||||
"login.error": "登录异常",
|
||||
"login.password_condition": "密码最多 60 位",
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
"forbid_hint": "停用後,該邀請連結將失效。該操作不可撤銷,是否確認停用?",
|
||||
"forbid_success": "停用成功",
|
||||
"forbidden": "停用",
|
||||
"link_forbidden": "禁用",
|
||||
"forbidden_tip": "確認將 {{username}} 禁用?成員將被標記為“禁用”並無法登錄,不刪除操作數據,賬號下資源自動轉讓給團隊所有者。",
|
||||
"group": "群組",
|
||||
"group_name": "群組名稱",
|
||||
"handle_invitation": "處理團隊邀請",
|
||||
|
|
@ -113,6 +113,7 @@
|
|||
"label_sync": "標籤同步",
|
||||
"leave": "已離職",
|
||||
"leave_team_failed": "離開團隊異常",
|
||||
"link_forbidden": "禁用",
|
||||
"log_admin_add_plan": "【{{name}}】將給團隊id為【{{teamId}}】的團隊添加了套餐",
|
||||
"log_admin_add_user": "【{{name}}】創建了一個名為【{{userName}}】的用戶",
|
||||
"log_admin_change_license": "【{{name}}】變更了License",
|
||||
|
|
@ -196,6 +197,7 @@
|
|||
"log_user": "操作人員",
|
||||
"login": "登入",
|
||||
"manage_member": "管理成員",
|
||||
"manage_per": "管理權限",
|
||||
"member": "成員",
|
||||
"member_group": "所屬成員組",
|
||||
"move_app": "應用位置移動",
|
||||
|
|
@ -222,7 +224,6 @@
|
|||
"relocate_department": "部門移動",
|
||||
"remark": "備註",
|
||||
"remove_tip": "確認將 {{username}} 移出團隊?成員將被標記為“離開”,不刪除操作數據,賬號下資源自動轉讓給團隊所有者。",
|
||||
"forbidden_tip": "確認將 {{username}} 禁用?成員將被標記為“禁用”並無法登錄,不刪除操作數據,賬號下資源自動轉讓給團隊所有者。",
|
||||
"restore_tip": "確認將 {{username}} 加入團隊嗎?\n僅恢復該成員賬號可用性及相關權限,無法恢復賬號下資源。",
|
||||
"restore_tip_title": "恢復確認",
|
||||
"retain_admin_permissions": "保留管理員權限",
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@
|
|||
"module.type": "\"{{type}}\" 類型\n{{description}}",
|
||||
"modules.Title is required": "模組名稱不能空白",
|
||||
"month.unit": "號",
|
||||
"move.hint": "移動後,所選應用程式/資料夾將會繼承新資料夾的權限設定,原先的權限設定將會失效。",
|
||||
"move.hint": "移動後,所選應用/文件夾將繼承新文件夾的權限設置。",
|
||||
"move_app": "移動應用程式",
|
||||
"no_mcp_tools_list": "暫無數據,需先解析 MCP 地址",
|
||||
"node_not_intro": "這個節點沒有介紹",
|
||||
|
|
@ -189,7 +189,7 @@
|
|||
"pdf_enhance_parse": "PDF 增強解析",
|
||||
"pdf_enhance_parse_price": "{{price}}積分/頁",
|
||||
"pdf_enhance_parse_tips": "呼叫 PDF 識別模型進行解析,可以將其轉換成 Markdown 並保留文件中的圖片,同時也可以對掃描件進行識別,識別時間較長。",
|
||||
"permission.des.manage": "在寫入權限基礎上,可以設定發布通道、檢視對話紀錄、分配這個應用程式的權限",
|
||||
"permission.des.manage": "可配置發布渠道、查看日誌、分配應用權限",
|
||||
"permission.des.read": "可以使用這個應用程式進行對話",
|
||||
"permission.des.readChatLog": "可以檢視對話紀錄",
|
||||
"permission.des.write": "可以檢視和編輯應用程式",
|
||||
|
|
|
|||
|
|
@ -965,7 +965,7 @@
|
|||
"permission.Private Tip": "僅自己可用",
|
||||
"permission.Public": "團隊",
|
||||
"permission.Public Tip": "所有團隊成員可用",
|
||||
"permission.Remove InheritPermission Confirm": "此操作會導致權限繼承失效,是否繼續?",
|
||||
"permission.Remove InheritPermission Confirm": "此修改與繼承權限存在衝突,會導致權限繼承失效,是否進行?",
|
||||
"permission.Resume InheritPermission Confirm": "要恢復繼承上層資料夾的權限嗎?",
|
||||
"permission.Resume InheritPermission Failed": "恢復失敗",
|
||||
"permission.Resume InheritPermission Success": "恢復成功",
|
||||
|
|
@ -975,6 +975,7 @@
|
|||
"permission.change_owner_success": "擁有權轉移成功",
|
||||
"permission.change_owner_tip": "轉移後您的權限將不會保留",
|
||||
"permission.change_owner_to": "轉移給",
|
||||
"permission.common_member": "普通成員",
|
||||
"permission.manager": "管理員",
|
||||
"permission.read": "讀取權限",
|
||||
"permission.write": "寫入權限",
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@
|
|||
"llm_paragraph_mode_force_desc": "強制使用模型自動識別段落,並忽略原文本的段落(如有)",
|
||||
"loading": "加載中...",
|
||||
"max_chunk_size": "最大分塊大小",
|
||||
"move.hint": "移動後,所選資料集/資料夾將繼承新資料夾的權限設定,原先的權限設定將失效。",
|
||||
"move.hint": "移動後,所選知識庫/文件夾將繼承新文件夾的權限設置。",
|
||||
"noChildren": "無子目錄",
|
||||
"noSelectedFolder": "沒有選擇文件夾",
|
||||
"noSelectedId": "沒有選擇 ID",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
"delete.admin_success": "刪除管理員成功",
|
||||
"delete.failed": "刪除失敗",
|
||||
"delete.success": "刪除成功",
|
||||
"has_chosen": "已選擇",
|
||||
"login.Dingtalk": "釘釘登入",
|
||||
"login.error": "登入失敗",
|
||||
"login.password_condition": "密碼最多可輸入 60 個字元",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
"ahooks": "^3.9.4",
|
||||
"date-fns": "2.30.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"next": "14.2.32",
|
||||
"i18next": "23.16.8",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lexical": "0.12.6",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"dependencies": {
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"assert": "^2.1.0",
|
||||
"axios": "^1.8.2",
|
||||
"axios": "^1.12.1",
|
||||
"body-parser": "^1.20.3",
|
||||
"browserify-zlib": "^0.2.0",
|
||||
"buffer": "^6.0.3",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"dependencies": {
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"assert": "^2.1.0",
|
||||
"axios": "^1.8.2",
|
||||
"axios": "^1.12.1",
|
||||
"body-parser": "^1.20.3",
|
||||
"browserify-zlib": "^0.2.0",
|
||||
"buffer": "^6.0.3",
|
||||
|
|
|
|||
166
pnpm-lock.yaml
|
|
@ -46,7 +46,7 @@ importers:
|
|||
version: 10.1.4(socks@2.8.4)
|
||||
next-i18next:
|
||||
specifier: 15.4.2
|
||||
version: 15.4.2(i18next@23.16.8)(next@14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
version: 15.4.2(i18next@23.16.8)(next@14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
prettier:
|
||||
specifier: 3.2.4
|
||||
version: 3.2.4
|
||||
|
|
@ -75,8 +75,8 @@ importers:
|
|||
specifier: ^0.1.16
|
||||
version: 0.1.16(@types/node@20.14.0)
|
||||
axios:
|
||||
specifier: ^1.8.2
|
||||
version: 1.8.4
|
||||
specifier: ^1.12.1
|
||||
version: 1.12.1
|
||||
cron-parser:
|
||||
specifier: ^4.9.0
|
||||
version: 4.9.0
|
||||
|
|
@ -102,8 +102,8 @@ importers:
|
|||
specifier: ^5.1.3
|
||||
version: 5.1.3
|
||||
next:
|
||||
specifier: 14.2.28
|
||||
version: 14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
||||
specifier: 14.2.32
|
||||
version: 14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
||||
openai:
|
||||
specifier: 4.61.0
|
||||
version: 4.61.0(encoding@0.1.13)(zod@3.25.51)
|
||||
|
|
@ -163,8 +163,8 @@ importers:
|
|||
specifier: 2.4.10
|
||||
version: 2.4.10
|
||||
axios:
|
||||
specifier: ^1.8.2
|
||||
version: 1.8.4
|
||||
specifier: ^1.12.1
|
||||
version: 1.12.1
|
||||
bullmq:
|
||||
specifier: ^5.52.2
|
||||
version: 5.52.2
|
||||
|
|
@ -232,11 +232,11 @@ importers:
|
|||
specifier: ^3.11.3
|
||||
version: 3.13.0
|
||||
next:
|
||||
specifier: 14.2.28
|
||||
version: 14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
||||
specifier: 14.2.32
|
||||
version: 14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
||||
nextjs-cors:
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0(next@14.2.28(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))
|
||||
version: 2.2.0(next@14.2.32(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))
|
||||
node-cron:
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3
|
||||
|
|
@ -318,7 +318,7 @@ importers:
|
|||
version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
'@chakra-ui/next-js':
|
||||
specifier: 2.4.2
|
||||
version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)
|
||||
version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)
|
||||
'@chakra-ui/react':
|
||||
specifier: 2.10.7
|
||||
version: 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
|
@ -391,9 +391,12 @@ importers:
|
|||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
next:
|
||||
specifier: 14.2.32
|
||||
version: 14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
||||
next-i18next:
|
||||
specifier: 15.4.2
|
||||
version: 15.4.2(i18next@23.16.8)(next@14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
version: 15.4.2(i18next@23.16.8)(next@14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
papaparse:
|
||||
specifier: ^5.4.1
|
||||
version: 5.4.1
|
||||
|
|
@ -457,7 +460,7 @@ importers:
|
|||
version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
'@chakra-ui/next-js':
|
||||
specifier: 2.4.2
|
||||
version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)
|
||||
version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)
|
||||
'@chakra-ui/react':
|
||||
specifier: 2.10.7
|
||||
version: 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
|
@ -501,8 +504,8 @@ importers:
|
|||
specifier: ^3.7.11
|
||||
version: 3.8.4(react@18.3.1)
|
||||
axios:
|
||||
specifier: ^1.8.2
|
||||
version: 1.8.4
|
||||
specifier: ^1.12.1
|
||||
version: 1.12.1
|
||||
date-fns:
|
||||
specifier: 2.30.0
|
||||
version: 2.30.0
|
||||
|
|
@ -549,11 +552,11 @@ importers:
|
|||
specifier: ^5.1.3
|
||||
version: 5.1.3
|
||||
next:
|
||||
specifier: 14.2.28
|
||||
version: 14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
||||
specifier: 14.2.32
|
||||
version: 14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
||||
next-i18next:
|
||||
specifier: 15.4.2
|
||||
version: 15.4.2(i18next@23.16.8)(next@14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
version: 15.4.2(i18next@23.16.8)(next@14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
nprogress:
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0
|
||||
|
|
@ -2521,62 +2524,62 @@ packages:
|
|||
'@nestjs/platform-express':
|
||||
optional: true
|
||||
|
||||
'@next/env@14.2.28':
|
||||
resolution: {integrity: sha512-PAmWhJfJQlP+kxZwCjrVd9QnR5x0R3u0mTXTiZDgSd4h5LdXmjxCCWbN9kq6hkZBOax8Rm3xDW5HagWyJuT37g==}
|
||||
'@next/env@14.2.32':
|
||||
resolution: {integrity: sha512-n9mQdigI6iZ/DF6pCTwMKeWgF2e8lg7qgt5M7HXMLtyhZYMnf/u905M18sSpPmHL9MKp9JHo56C6jrD2EvWxng==}
|
||||
|
||||
'@next/eslint-plugin-next@14.2.26':
|
||||
resolution: {integrity: sha512-SPEj1O5DAVTPaWD9XPupelfT2APNIgcDYD2OzEm328BEmHaglhmYNUvxhzfJYDr12AgAfW4V3UHSV93qaeELJA==}
|
||||
|
||||
'@next/swc-darwin-arm64@14.2.28':
|
||||
resolution: {integrity: sha512-kzGChl9setxYWpk3H6fTZXXPFFjg7urptLq5o5ZgYezCrqlemKttwMT5iFyx/p1e/JeglTwDFRtb923gTJ3R1w==}
|
||||
'@next/swc-darwin-arm64@14.2.32':
|
||||
resolution: {integrity: sha512-osHXveM70zC+ilfuFa/2W6a1XQxJTvEhzEycnjUaVE8kpUS09lDpiDDX2YLdyFCzoUbvbo5r0X1Kp4MllIOShw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-x64@14.2.28':
|
||||
resolution: {integrity: sha512-z6FXYHDJlFOzVEOiiJ/4NG8aLCeayZdcRSMjPDysW297Up6r22xw6Ea9AOwQqbNsth8JNgIK8EkWz2IDwaLQcw==}
|
||||
'@next/swc-darwin-x64@14.2.32':
|
||||
resolution: {integrity: sha512-P9NpCAJuOiaHHpqtrCNncjqtSBi1f6QUdHK/+dNabBIXB2RUFWL19TY1Hkhu74OvyNQEYEzzMJCMQk5agjw1Qg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@14.2.28':
|
||||
resolution: {integrity: sha512-9ARHLEQXhAilNJ7rgQX8xs9aH3yJSj888ssSjJLeldiZKR4D7N08MfMqljk77fAwZsWwsrp8ohHsMvurvv9liQ==}
|
||||
'@next/swc-linux-arm64-gnu@14.2.32':
|
||||
resolution: {integrity: sha512-v7JaO0oXXt6d+cFjrrKqYnR2ubrD+JYP7nQVRZgeo5uNE5hkCpWnHmXm9vy3g6foMO8SPwL0P3MPw1c+BjbAzA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-arm64-musl@14.2.28':
|
||||
resolution: {integrity: sha512-p6gvatI1nX41KCizEe6JkF0FS/cEEF0u23vKDpl+WhPe/fCTBeGkEBh7iW2cUM0rvquPVwPWdiUR6Ebr/kQWxQ==}
|
||||
'@next/swc-linux-arm64-musl@14.2.32':
|
||||
resolution: {integrity: sha512-tA6sIKShXtSJBTH88i0DRd6I9n3ZTirmwpwAqH5zdJoQF7/wlJXR8DkPmKwYl5mFWhEKr5IIa3LfpMW9RRwKmQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-gnu@14.2.28':
|
||||
resolution: {integrity: sha512-nsiSnz2wO6GwMAX2o0iucONlVL7dNgKUqt/mDTATGO2NY59EO/ZKnKEr80BJFhuA5UC1KZOMblJHWZoqIJddpA==}
|
||||
'@next/swc-linux-x64-gnu@14.2.32':
|
||||
resolution: {integrity: sha512-7S1GY4TdnlGVIdeXXKQdDkfDysoIVFMD0lJuVVMeb3eoVjrknQ0JNN7wFlhCvea0hEk0Sd4D1hedVChDKfV2jw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-musl@14.2.28':
|
||||
resolution: {integrity: sha512-+IuGQKoI3abrXFqx7GtlvNOpeExUH1mTIqCrh1LGFf8DnlUcTmOOCApEnPJUSLrSbzOdsF2ho2KhnQoO0I1RDw==}
|
||||
'@next/swc-linux-x64-musl@14.2.32':
|
||||
resolution: {integrity: sha512-OHHC81P4tirVa6Awk6eCQ6RBfWl8HpFsZtfEkMpJ5GjPsJ3nhPe6wKAJUZ/piC8sszUkAgv3fLflgzPStIwfWg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@14.2.28':
|
||||
resolution: {integrity: sha512-l61WZ3nevt4BAnGksUVFKy2uJP5DPz2E0Ma/Oklvo3sGj9sw3q7vBWONFRgz+ICiHpW5mV+mBrkB3XEubMrKaA==}
|
||||
'@next/swc-win32-arm64-msvc@14.2.32':
|
||||
resolution: {integrity: sha512-rORQjXsAFeX6TLYJrCG5yoIDj+NKq31Rqwn8Wpn/bkPNy5rTHvOXkW8mLFonItS7QC6M+1JIIcLe+vOCTOYpvg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-ia32-msvc@14.2.28':
|
||||
resolution: {integrity: sha512-+Kcp1T3jHZnJ9v9VTJ/yf1t/xmtFAc/Sge4v7mVc1z+NYfYzisi8kJ9AsY8itbgq+WgEwMtOpiLLJsUy2qnXZw==}
|
||||
'@next/swc-win32-ia32-msvc@14.2.32':
|
||||
resolution: {integrity: sha512-jHUeDPVHrgFltqoAqDB6g6OStNnFxnc7Aks3p0KE0FbwAvRg6qWKYF5mSTdCTxA3axoSAUwxYdILzXJfUwlHhA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-x64-msvc@14.2.28':
|
||||
resolution: {integrity: sha512-1gCmpvyhz7DkB1srRItJTnmR2UwQPAUXXIg9r0/56g3O8etGmwlX68skKXJOp9EejW3hhv7nSQUJ2raFiz4MoA==}
|
||||
'@next/swc-win32-x64-msvc@14.2.32':
|
||||
resolution: {integrity: sha512-2N0lSoU4GjfLSO50wvKpMQgKd4HdI2UHEhQPPPnlgfBJlOgJxkjpkYBqzk08f1gItBB6xF/n+ykso2hgxuydsA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
|
@ -4105,8 +4108,8 @@ packages:
|
|||
resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
axios@1.8.4:
|
||||
resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==}
|
||||
axios@1.12.1:
|
||||
resolution: {integrity: sha512-Kn4kbSXpkFHCGE6rBFNwIv0GQs4AvDT80jlveJDKFxjbTYMUeB4QtsdPCv6H8Cm19Je7IU6VFtRl2zWZI0rudQ==}
|
||||
|
||||
axobject-query@4.1.0:
|
||||
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
|
||||
|
|
@ -5664,10 +5667,6 @@ packages:
|
|||
resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==}
|
||||
engines: {node: '>= 14.17'}
|
||||
|
||||
form-data@4.0.2:
|
||||
resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
form-data@4.0.4:
|
||||
resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
|
||||
engines: {node: '>= 6'}
|
||||
|
|
@ -7444,8 +7443,8 @@ packages:
|
|||
react: '>= 17.0.2'
|
||||
react-i18next: '>= 13.5.0'
|
||||
|
||||
next@14.2.28:
|
||||
resolution: {integrity: sha512-QLEIP/kYXynIxtcKB6vNjtWLVs3Y4Sb+EClTC/CSVzdLD1gIuItccpu/n1lhmduffI32iPGEK2cLLxxt28qgYA==}
|
||||
next@14.2.32:
|
||||
resolution: {integrity: sha512-fg5g0GZ7/nFc09X8wLe6pNSU8cLWbLRG3TZzPJ1BJvi2s9m7eF991se67wliM9kR5yLHRkyGKU49MMx58s3LJg==}
|
||||
engines: {node: '>=18.17.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
|
|
@ -10796,12 +10795,12 @@ snapshots:
|
|||
'@chakra-ui/system': 2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
|
||||
'@chakra-ui/next-js@2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)':
|
||||
'@chakra-ui/next-js@2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@chakra-ui/react': 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@emotion/cache': 11.14.0
|
||||
'@emotion/react': 11.11.1(@types/react@18.3.1)(react@18.3.1)
|
||||
next: 14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
||||
next: 14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
||||
react: 18.3.1
|
||||
|
||||
'@chakra-ui/object-utils@2.1.0': {}
|
||||
|
|
@ -11922,37 +11921,37 @@ snapshots:
|
|||
'@nestjs/core': 10.4.15(@nestjs/common@10.4.16(reflect-metadata@0.2.2)(rxjs@7.8.2))(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
tslib: 2.8.1
|
||||
|
||||
'@next/env@14.2.28': {}
|
||||
'@next/env@14.2.32': {}
|
||||
|
||||
'@next/eslint-plugin-next@14.2.26':
|
||||
dependencies:
|
||||
glob: 10.3.10
|
||||
|
||||
'@next/swc-darwin-arm64@14.2.28':
|
||||
'@next/swc-darwin-arm64@14.2.32':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@14.2.28':
|
||||
'@next/swc-darwin-x64@14.2.32':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@14.2.28':
|
||||
'@next/swc-linux-arm64-gnu@14.2.32':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@14.2.28':
|
||||
'@next/swc-linux-arm64-musl@14.2.32':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@14.2.28':
|
||||
'@next/swc-linux-x64-gnu@14.2.32':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@14.2.28':
|
||||
'@next/swc-linux-x64-musl@14.2.32':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@14.2.28':
|
||||
'@next/swc-win32-arm64-msvc@14.2.32':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-ia32-msvc@14.2.28':
|
||||
'@next/swc-win32-ia32-msvc@14.2.32':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@14.2.28':
|
||||
'@next/swc-win32-x64-msvc@14.2.32':
|
||||
optional: true
|
||||
|
||||
'@node-rs/jieba-android-arm-eabi@2.0.1':
|
||||
|
|
@ -13699,10 +13698,10 @@ snapshots:
|
|||
|
||||
axe-core@4.10.3: {}
|
||||
|
||||
axios@1.8.4:
|
||||
axios@1.12.1:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.9(debug@4.4.0)
|
||||
form-data: 4.0.2
|
||||
form-data: 4.0.4
|
||||
proxy-from-env: 1.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
|
@ -15141,7 +15140,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0)(eslint@8.56.0):
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0)(eslint@8.56.0))(eslint@8.56.0):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
|
|
@ -15152,7 +15151,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0)(eslint@8.57.1):
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
|
|
@ -15174,7 +15173,7 @@ snapshots:
|
|||
doctrine: 2.1.0
|
||||
eslint: 8.56.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0)(eslint@8.56.0)
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0)(eslint@8.56.0))(eslint@8.56.0)
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
|
|
@ -15203,7 +15202,7 @@ snapshots:
|
|||
doctrine: 2.1.0
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0)(eslint@8.57.1)
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1)
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
|
|
@ -15817,13 +15816,6 @@ snapshots:
|
|||
|
||||
form-data-encoder@2.1.4: {}
|
||||
|
||||
form-data@4.0.2:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
es-set-tostringtag: 2.1.0
|
||||
mime-types: 2.1.35
|
||||
|
||||
form-data@4.0.4:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
|
|
@ -18201,7 +18193,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
next-i18next@15.4.2(i18next@23.16.8)(next@14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
|
||||
next-i18next@15.4.2(i18next@23.16.8)(next@14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.10
|
||||
'@types/hoist-non-react-statics': 3.3.6
|
||||
|
|
@ -18209,13 +18201,13 @@ snapshots:
|
|||
hoist-non-react-statics: 3.3.2
|
||||
i18next: 23.16.8
|
||||
i18next-fs-backend: 2.6.0
|
||||
next: 14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
||||
next: 14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
||||
react: 18.3.1
|
||||
react-i18next: 14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
||||
next@14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1):
|
||||
next@14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1):
|
||||
dependencies:
|
||||
'@next/env': 14.2.28
|
||||
'@next/env': 14.2.32
|
||||
'@swc/helpers': 0.5.5
|
||||
busboy: 1.6.0
|
||||
caniuse-lite: 1.0.30001704
|
||||
|
|
@ -18225,25 +18217,25 @@ snapshots:
|
|||
react-dom: 18.3.1(react@18.3.1)
|
||||
styled-jsx: 5.1.1(@babel/core@7.26.10)(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 14.2.28
|
||||
'@next/swc-darwin-x64': 14.2.28
|
||||
'@next/swc-linux-arm64-gnu': 14.2.28
|
||||
'@next/swc-linux-arm64-musl': 14.2.28
|
||||
'@next/swc-linux-x64-gnu': 14.2.28
|
||||
'@next/swc-linux-x64-musl': 14.2.28
|
||||
'@next/swc-win32-arm64-msvc': 14.2.28
|
||||
'@next/swc-win32-ia32-msvc': 14.2.28
|
||||
'@next/swc-win32-x64-msvc': 14.2.28
|
||||
'@next/swc-darwin-arm64': 14.2.32
|
||||
'@next/swc-darwin-x64': 14.2.32
|
||||
'@next/swc-linux-arm64-gnu': 14.2.32
|
||||
'@next/swc-linux-arm64-musl': 14.2.32
|
||||
'@next/swc-linux-x64-gnu': 14.2.32
|
||||
'@next/swc-linux-x64-musl': 14.2.32
|
||||
'@next/swc-win32-arm64-msvc': 14.2.32
|
||||
'@next/swc-win32-ia32-msvc': 14.2.32
|
||||
'@next/swc-win32-x64-msvc': 14.2.32
|
||||
'@opentelemetry/api': 1.9.0
|
||||
sass: 1.85.1
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
nextjs-cors@2.2.0(next@14.2.28(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)):
|
||||
nextjs-cors@2.2.0(next@14.2.32(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)):
|
||||
dependencies:
|
||||
cors: 2.8.5
|
||||
next: 14.2.28(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
||||
next: 14.2.32(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
||||
|
||||
node-abi@3.74.0:
|
||||
dependencies:
|
||||
|
|
@ -20007,7 +19999,7 @@ snapshots:
|
|||
terser@5.39.0:
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.6
|
||||
acorn: 8.14.1
|
||||
acorn: 8.15.0
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
"@node-rs/jieba": "2.0.1",
|
||||
"@tanstack/react-query": "^4.24.10",
|
||||
"ahooks": "^3.7.11",
|
||||
"axios": "^1.8.2",
|
||||
"axios": "^1.12.1",
|
||||
"date-fns": "2.30.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"echarts": "5.4.1",
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
"lodash": "^4.17.21",
|
||||
"mermaid": "^10.9.4",
|
||||
"nanoid": "^5.1.3",
|
||||
"next": "14.2.28",
|
||||
"next": "14.2.32",
|
||||
"next-i18next": "15.4.2",
|
||||
"nprogress": "^0.2.0",
|
||||
"qrcode": "^1.5.4",
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { getCouponCode, removeCouponCode } from '@/web/support/marketing/utils';
|
||||
import type { UserType } from '@fastgpt/global/support/user/type.d';
|
||||
import { redeemCoupon } from '@/web/support/user/team/api';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
|
||||
export const useCheckCoupon = (userInfo: UserType | null) => {
|
||||
const hasCheckedCouponRef = useRef(false);
|
||||
export const useCheckCoupon = () => {
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (!userInfo || hasCheckedCouponRef.current) return;
|
||||
if (!userInfo) return;
|
||||
|
||||
const couponCode = getCouponCode();
|
||||
if (!couponCode) return;
|
||||
|
||||
hasCheckedCouponRef.current = true;
|
||||
|
||||
redeemCoupon(couponCode)
|
||||
.catch(() => {})
|
||||
.finally(removeCouponCode);
|
||||
.then(removeCouponCode)
|
||||
.catch((err) => {
|
||||
if (err?.message === 'Invalid coupon') {
|
||||
removeCouponCode();
|
||||
}
|
||||
});
|
||||
}, [userInfo]);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
|||
const { userInfo, isUpdateNotification, setIsUpdateNotification } = useUserStore();
|
||||
const { setUserDefaultLng } = useI18nLng();
|
||||
|
||||
useCheckCoupon(userInfo);
|
||||
useCheckCoupon();
|
||||
|
||||
const isChatPage = useMemo(
|
||||
() => router.pathname === '/chat' && Object.values(router.query).join('').length !== 0,
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ const FolderSlideCard = ({
|
|||
isInheritPermission={isInheritPermission}
|
||||
hasParent={hasParent}
|
||||
>
|
||||
{({ MemberListCard, onOpenManageModal, onOpenAddMember }) => {
|
||||
{({ MemberListCard, onOpenManageModal }) => {
|
||||
return (
|
||||
<>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
|
|
@ -145,26 +145,15 @@ const FolderSlideCard = ({
|
|||
{t('common:permission.Collaborator')}
|
||||
</Box>
|
||||
{managePer.permission.hasManagePer && (
|
||||
<HStack spacing={3}>
|
||||
<MyTooltip label={t('common:permission.Manage')}>
|
||||
<MyIcon
|
||||
w="1rem"
|
||||
name="common/settingLight"
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={onOpenManageModal}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<MyTooltip label={t('common:Add')}>
|
||||
<MyIcon
|
||||
w="1rem"
|
||||
name="support/permission/collaborator"
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={onOpenAddMember}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</HStack>
|
||||
<MyTooltip label={t('common:permission.Manage')}>
|
||||
<MyIcon
|
||||
w="1rem"
|
||||
name="common/settingLight"
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={onOpenManageModal}
|
||||
/>
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Flex>
|
||||
<MemberListCard
|
||||
|
|
|
|||
|
|
@ -124,19 +124,21 @@ const VariableInput = ({
|
|||
bg={'white'}
|
||||
boxShadow={'0 0 8px rgba(0,0,0,0.15)'}
|
||||
>
|
||||
<Flex
|
||||
color={'primary.600'}
|
||||
bg={'primary.100'}
|
||||
mb={3}
|
||||
px={3}
|
||||
py={1.5}
|
||||
gap={1}
|
||||
fontSize={'mini'}
|
||||
rounded={'sm'}
|
||||
>
|
||||
<MyIcon name={'common/info'} color={'primary.600'} w={4} />
|
||||
{t('chat:variable_invisable_in_share')}
|
||||
</Flex>
|
||||
{chatType !== ChatTypeEnum.chat && (
|
||||
<Flex
|
||||
color={'primary.600'}
|
||||
bg={'primary.100'}
|
||||
mb={3}
|
||||
px={3}
|
||||
py={1.5}
|
||||
gap={1}
|
||||
fontSize={'mini'}
|
||||
rounded={'sm'}
|
||||
>
|
||||
<MyIcon name={'common/info'} color={'primary.600'} w={4} />
|
||||
{t('chat:variable_invisable_in_share')}
|
||||
</Flex>
|
||||
)}
|
||||
{externalVariableList.map((item) => {
|
||||
return (
|
||||
<LabelAndFormRender
|
||||
|
|
|
|||
|
|
@ -33,19 +33,6 @@ const ChatHomeVariablesForm = ({ chatForm }: Props) => {
|
|||
{/* custom variables */}
|
||||
{allVariableList.filter((i) => i.type === VariableInputEnum.custom).length > 0 && (
|
||||
<>
|
||||
<Flex
|
||||
color={'primary.600'}
|
||||
bg={'primary.100'}
|
||||
mb={3}
|
||||
px={3}
|
||||
py={1.5}
|
||||
gap={1}
|
||||
fontSize={'mini'}
|
||||
rounded={'sm'}
|
||||
>
|
||||
<MyIcon name={'common/info'} color={'primary.600'} w={4} />
|
||||
{t('chat:variable_invisable_in_share')}
|
||||
</Flex>
|
||||
{allVariableList
|
||||
.filter((i) => i.type === VariableInputEnum.custom)
|
||||
.map((item) => (
|
||||
|
|
|
|||
|
|
@ -1126,7 +1126,8 @@ const ChatBox = ({
|
|||
>
|
||||
<Flex h={'100%'} flexDir={'column'} justifyContent={'center'} w={'100%'}>
|
||||
{HomeChatRenderBox}
|
||||
{allVariableList.length > 0 ? (
|
||||
{allVariableList.filter((item) => item.type !== VariableInputEnum.internal).length >
|
||||
0 ? (
|
||||
<Box w={'100%'}>
|
||||
<ChatHomeVariablesForm chatForm={chatForm} />
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -118,19 +118,21 @@ const VariablePopover = ({ chatType }: { chatType: ChatTypeEnum }) => {
|
|||
|
||||
{externalVariableList.length > 0 && (
|
||||
<Box textAlign={'left'}>
|
||||
<Flex
|
||||
color={'primary.600'}
|
||||
bg={'primary.100'}
|
||||
mb={3}
|
||||
px={3}
|
||||
py={1.5}
|
||||
gap={1}
|
||||
fontSize={'mini'}
|
||||
rounded={'sm'}
|
||||
>
|
||||
<MyIcon name={'common/info'} color={'primary.600'} w={4} />
|
||||
{t('chat:variable_invisable_in_share')}
|
||||
</Flex>
|
||||
{chatType !== ChatTypeEnum.chat && (
|
||||
<Flex
|
||||
color={'primary.600'}
|
||||
bg={'primary.100'}
|
||||
mb={3}
|
||||
px={3}
|
||||
py={1.5}
|
||||
gap={1}
|
||||
fontSize={'mini'}
|
||||
rounded={'sm'}
|
||||
>
|
||||
<MyIcon name={'common/info'} color={'primary.600'} w={4} />
|
||||
{t('chat:variable_invisable_in_share')}
|
||||
</Flex>
|
||||
)}
|
||||
{externalVariableList.map((item) => (
|
||||
<LabelAndFormRender
|
||||
{...item}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
import InputRender from '@/components/core/app/formRender';
|
||||
import { nodeInputTypeToInputType } from '@/components/core/app/formRender/utils';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import LeftRadio from '@fastgpt/web/components/common/Radio/LeftRadio';
|
||||
|
||||
const DescriptionBox = React.memo(function DescriptionBox({
|
||||
description
|
||||
|
|
@ -38,33 +39,26 @@ export const SelectOptionsComponent = React.memo(function SelectOptionsComponent
|
|||
return (
|
||||
<Box maxW={'100%'}>
|
||||
<DescriptionBox description={description} />
|
||||
<Flex flexDirection={'column'} gap={3} w={'250px'}>
|
||||
{userSelectOptions.map((option: UserSelectOptionItemType) => {
|
||||
const selected = option.value === userSelectedVal;
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={option.key}
|
||||
variant={'whitePrimary'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
isDisabled={!!userSelectedVal}
|
||||
{...(selected
|
||||
? {
|
||||
_disabled: {
|
||||
cursor: 'default',
|
||||
borderColor: 'primary.300',
|
||||
bg: 'primary.50 !important',
|
||||
color: 'primary.600'
|
||||
}
|
||||
}
|
||||
: {})}
|
||||
onClick={() => onSelect(option.value)}
|
||||
>
|
||||
{option.value}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
<Box w={'250px'}>
|
||||
<LeftRadio<string>
|
||||
py={3.5}
|
||||
gridGap={3}
|
||||
align={'center'}
|
||||
list={userSelectOptions.map((option: UserSelectOptionItemType) => ({
|
||||
title: (
|
||||
<Box fontSize={'sm'} whiteSpace={'pre-wrap'} wordBreak={'break-word'}>
|
||||
{option.value}
|
||||
</Box>
|
||||
),
|
||||
value: option.value
|
||||
}))}
|
||||
value={userSelectedVal || ''}
|
||||
defaultBg={'white'}
|
||||
activeBg={'white'}
|
||||
onChange={(val) => onSelect(val)}
|
||||
isDisabled={!!userSelectedVal}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ const ConfigPerModal = ({
|
|||
isInheritPermission={isInheritPermission}
|
||||
hasParent={hasParent}
|
||||
>
|
||||
{({ MemberListCard, onOpenManageModal, onOpenAddMember }) => {
|
||||
{({ MemberListCard, onOpenManageModal }) => {
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
|
|
@ -77,24 +77,14 @@ const ConfigPerModal = ({
|
|||
w="full"
|
||||
>
|
||||
<Box fontSize={'sm'}>{t('common:permission.Collaborator')}</Box>
|
||||
<Flex flexDirection="row" gap="2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="whitePrimary"
|
||||
leftIcon={<MyIcon w="4" name="common/settingLight" />}
|
||||
onClick={onOpenManageModal}
|
||||
>
|
||||
{t('common:permission.Manage')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="whitePrimary"
|
||||
leftIcon={<MyIcon w="4" name="support/permission/collaborator" />}
|
||||
onClick={onOpenAddMember}
|
||||
>
|
||||
{t('common:Add')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="whitePrimary"
|
||||
leftIcon={<MyIcon w="4" name="common/settingLight" />}
|
||||
onClick={onOpenManageModal}
|
||||
>
|
||||
{t('common:permission.Manage')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<MemberListCard mt={2} p={1.5} bg="myGray.100" borderRadius="md" />
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,119 +0,0 @@
|
|||
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 RoleSelect from './RoleSelect';
|
||||
import RoleTags from './RoleTags';
|
||||
import { CollaboratorContext } from './context';
|
||||
export type ManageModalProps = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
function ManageModal({ onClose }: ManageModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
const { permission, collaboratorList, onUpdateCollaborators, onDelOneCollaborator } =
|
||||
useContextSelector(CollaboratorContext, (v) => v);
|
||||
|
||||
const { runAsync: onDelete, loading: isDeleting } = useRequest2(onDelOneCollaborator);
|
||||
|
||||
const { runAsync: onUpdate, loading: isUpdating } = useRequest2(onUpdateCollaborators, {
|
||||
successToast: t('common:update_success'),
|
||||
errorToast: 'Error'
|
||||
});
|
||||
|
||||
const loading = isDeleting || isUpdating;
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
minW="600px"
|
||||
title={t('user:team.manage_collaborators')}
|
||||
iconSrc="common/settingLight"
|
||||
>
|
||||
<ModalBody>
|
||||
<TableContainer borderRadius="md" minH="400px">
|
||||
<Table>
|
||||
<Thead bg="myGray.100">
|
||||
<Tr>
|
||||
<Th border="none">{t('user:name')}</Th>
|
||||
<Th border="none">{t('user:permissions')}</Th>
|
||||
<Th border="none" w={'40px'}>
|
||||
{t('user:operations')}
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr h={'10px'} />
|
||||
{collaboratorList?.map((item) => {
|
||||
return (
|
||||
<Tr
|
||||
key={item.tmbId}
|
||||
_hover={{
|
||||
bg: 'myGray.50'
|
||||
}}
|
||||
>
|
||||
<Td border="none">
|
||||
<Flex alignItems="center">
|
||||
<Avatar src={item.avatar} rounded={'50%'} w="24px" mr={2} />
|
||||
{item.name === DefaultGroupName ? userInfo?.team.teamName : item.name}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td border="none">
|
||||
<RoleTags permission={item.permission.role} />
|
||||
</Td>
|
||||
<Td border="none">
|
||||
{/* Not self; Not owner and other manager */}
|
||||
{item.tmbId !== userInfo?.team?.tmbId &&
|
||||
(permission.isOwner || !item.permission.hasManagePer) && (
|
||||
<RoleSelect
|
||||
Button={
|
||||
<MyIcon name={'edit'} w={'16px'} _hover={{ color: 'primary.600' }} />
|
||||
}
|
||||
value={item.permission.role}
|
||||
onChange={(permission) => {
|
||||
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,
|
||||
orgId: item.orgId
|
||||
} as RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
{collaboratorList?.length === 0 && <EmptyTip text={t('user:team.no_collaborators')} />}
|
||||
</TableContainer>
|
||||
{loading && <Loading fixed={false} />}
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ManageModal;
|
||||
|
|
@ -1,92 +1,128 @@
|
|||
import React from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Checkbox, HStack, VStack } from '@chakra-ui/react';
|
||||
import { Box, Checkbox, Flex } from '@chakra-ui/react';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import RoleTags from './RoleTags';
|
||||
import type { RoleValueType } from '@fastgpt/global/support/permission/type';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import OrgTags from '../../user/team/OrgTags';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
import RoleSelect from './RoleSelect';
|
||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
|
||||
function MemberItemCard({
|
||||
avatar,
|
||||
key,
|
||||
onChange: _onChange,
|
||||
onChange,
|
||||
isChecked,
|
||||
onDelete,
|
||||
name,
|
||||
role,
|
||||
orgs,
|
||||
addOnly,
|
||||
rightSlot
|
||||
rightSlot,
|
||||
onRoleChange,
|
||||
disabled = false
|
||||
}: {
|
||||
avatar: string;
|
||||
key: string;
|
||||
onChange: () => void;
|
||||
onChange?: () => void;
|
||||
onRoleChange?: (role: RoleValueType) => void;
|
||||
isChecked?: boolean;
|
||||
onDelete?: () => void;
|
||||
name: string;
|
||||
role?: RoleValueType;
|
||||
addOnly?: boolean;
|
||||
orgs?: string[];
|
||||
rightSlot?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const isAdded = addOnly && !!role;
|
||||
const onChange = () => {
|
||||
if (!isAdded) _onChange();
|
||||
};
|
||||
const { t } = useTranslation();
|
||||
const showRoleSelect = onRoleChange !== undefined;
|
||||
const { userInfo } = useUserStore();
|
||||
return (
|
||||
<HStack
|
||||
<Flex
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
key={key}
|
||||
px="3"
|
||||
py="2"
|
||||
px="1"
|
||||
py="1"
|
||||
gap="2"
|
||||
borderRadius="sm"
|
||||
_hover={{
|
||||
bgColor: 'myGray.50',
|
||||
cursor: 'pointer'
|
||||
{...(!showRoleSelect
|
||||
? {
|
||||
_hover: { bgColor: 'myGray.50' },
|
||||
cursor: 'pointer'
|
||||
}
|
||||
: {})}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
onChange?.();
|
||||
}}
|
||||
onClick={onChange}
|
||||
>
|
||||
{isChecked !== undefined && (
|
||||
<Checkbox isChecked={isChecked} pointerEvents="none" disabled={isAdded} />
|
||||
)}
|
||||
<Avatar src={avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
|
||||
<Box w="full">
|
||||
<Box fontSize={'sm'} className="textEllipsis" maxW="300px">
|
||||
{name}
|
||||
<Flex
|
||||
flexDirection={'row'}
|
||||
h={showRoleSelect ? '36px' : 'unset'}
|
||||
p="1"
|
||||
alignItems={'center'}
|
||||
gap="2"
|
||||
w="full"
|
||||
>
|
||||
{isChecked !== undefined && (
|
||||
<Checkbox isDisabled={disabled} isChecked={isChecked} pointerEvents="none" />
|
||||
)}
|
||||
<Avatar src={avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box flex={'1 0 0'} w={0}>
|
||||
<Box fontSize={'sm'} w={'100%'} noOfLines={1}>
|
||||
{name === DefaultGroupName ? userInfo?.team.teamName : name}
|
||||
</Box>
|
||||
<Box lineHeight={1} w={'100%'}>
|
||||
{orgs && orgs.length > 0 && <OrgTags orgs={orgs} />}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box lineHeight={1}>{orgs && orgs.length > 0 && <OrgTags orgs={orgs} />}</Box>
|
||||
</Box>
|
||||
{!isAdded && role && <RoleTags permission={role} />}
|
||||
{isAdded && (
|
||||
<Tag
|
||||
mixBlendMode={'multiply'}
|
||||
colorSchema="blue"
|
||||
border="none"
|
||||
py={2}
|
||||
px={3}
|
||||
fontSize={'xs'}
|
||||
>
|
||||
{t('user:team.collaborator.added')}
|
||||
</Tag>
|
||||
)}
|
||||
{onDelete !== undefined && (
|
||||
<MyIcon
|
||||
name="common/closeLight"
|
||||
w="1rem"
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'red.600'
|
||||
}}
|
||||
onClick={onDelete}
|
||||
</Flex>
|
||||
{showRoleSelect && (
|
||||
<RoleSelect
|
||||
disabled={disabled}
|
||||
value={role}
|
||||
Button={
|
||||
<Flex
|
||||
bg={'myGray.50'}
|
||||
border="base"
|
||||
fontSize={'sm'}
|
||||
borderRadius={'md'}
|
||||
minH={'18px'}
|
||||
w="300px"
|
||||
p="1"
|
||||
alignItems={'end'}
|
||||
justifyContent={'space-between'}
|
||||
>
|
||||
<RoleTags permission={role} />
|
||||
<Flex h="18px" alignItems={'center'} justifyContent={'center'}>
|
||||
<ChevronDownIcon fontSize="md" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
}
|
||||
onChange={onRoleChange}
|
||||
/>
|
||||
)}
|
||||
{rightSlot}
|
||||
</HStack>
|
||||
<Flex flexDirection={'row'} h={showRoleSelect ? '36px' : 'unset'} alignItems={'center'}>
|
||||
{onDelete !== undefined && !disabled ? (
|
||||
<MyIcon
|
||||
name="common/closeLight"
|
||||
w="1rem"
|
||||
cursor={disabled ? 'not-allowed' : 'pointer'}
|
||||
_hover={{
|
||||
color: 'red.600'
|
||||
}}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
onDelete?.();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Box minW="16px"></Box>
|
||||
)}
|
||||
</Flex>
|
||||
{!!rightSlot && rightSlot}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,17 +3,13 @@ import { getTeamMembers } from '@/web/support/user/team/api';
|
|||
import { getGroupList } from '@/web/support/user/team/group/api';
|
||||
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||
import { Box, Button, Flex, Grid, HStack, ModalBody, ModalFooter, Text } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, Grid, HStack, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import {
|
||||
DEFAULT_ORG_AVATAR,
|
||||
DEFAULT_TEAM_AVATAR,
|
||||
DEFAULT_USER_AVATAR
|
||||
} from '@fastgpt/global/common/system/constants';
|
||||
import { type UpdateClbPermissionProps } from '@fastgpt/global/support/permission/collaborator';
|
||||
import { type MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import { type OrgListItemType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import { type TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
||||
import MyAvatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
|
@ -22,28 +18,36 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
|
|||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { type ValueOf } from 'next/dist/shared/lib/constants';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { CollaboratorContext } from './context';
|
||||
import MemberItemCard from './MemberItemCard';
|
||||
import RoleSelect from './RoleSelect';
|
||||
import type {
|
||||
CollaboratorItemDetailType,
|
||||
CollaboratorItemType
|
||||
} from '@fastgpt/global/support/permission/collaborator';
|
||||
import type { RoleValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
import {
|
||||
checkRoleUpdateConflict,
|
||||
getCollaboratorId,
|
||||
mergeCollaboratorList
|
||||
} from '@fastgpt/global/support/permission/utils';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { ManageRoleVal, OwnerRoleVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { isObjectIdOrHexString } from 'mongoose';
|
||||
|
||||
const HoverBoxStyle = {
|
||||
bgColor: 'myGray.50',
|
||||
cursor: 'pointer'
|
||||
};
|
||||
|
||||
function MemberModal({
|
||||
onClose,
|
||||
addPermissionOnly: addOnly = false
|
||||
}: {
|
||||
onClose: () => void;
|
||||
addPermissionOnly?: boolean;
|
||||
}) {
|
||||
function MemberModal({ onClose }: { onClose: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList);
|
||||
const collaboratorDetailList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList);
|
||||
const isInheritPermission = useContextSelector(CollaboratorContext, (v) => v.isInheritPermission);
|
||||
const defaultRole = useContextSelector(CollaboratorContext, (v) => v.defaultRole);
|
||||
const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>();
|
||||
const {
|
||||
paths,
|
||||
|
|
@ -56,11 +60,7 @@ function MemberModal({
|
|||
setSearchKey
|
||||
} = useOrg({ withPermission: false });
|
||||
|
||||
const {
|
||||
data: members,
|
||||
ScrollData: TeamMemberScrollData,
|
||||
refreshList
|
||||
} = useScrollPagination(getTeamMembers, {
|
||||
const { data: members, ScrollData: TeamMemberScrollData } = useScrollPagination(getTeamMembers, {
|
||||
pageSize: 15,
|
||||
params: {
|
||||
withPermission: true,
|
||||
|
|
@ -73,11 +73,7 @@ function MemberModal({
|
|||
refreshDeps: [searchKey]
|
||||
});
|
||||
|
||||
const {
|
||||
data: groups = [],
|
||||
loading: loadingGroupsAndOrgs,
|
||||
runAsync: refreshGroups
|
||||
} = useRequest2(
|
||||
const { data: groups = [], loading: loadingGroupsAndOrgs } = useRequest2(
|
||||
async () => {
|
||||
if (!userInfo?.team?.teamId) return [];
|
||||
return getGroupList<false>({
|
||||
|
|
@ -91,34 +87,33 @@ function MemberModal({
|
|||
}
|
||||
);
|
||||
|
||||
const [selectedOrgList, setSelectedOrgIdList] = useState<OrgListItemType[]>([]);
|
||||
const [editCollaborators, setCollaboratorList] = useState<CollaboratorItemDetailType[]>([]);
|
||||
|
||||
const [selectedMemberList, setSelectedMemberList] = useState<
|
||||
Omit<TeamMemberItemType, 'permission' | 'teamId'>[]
|
||||
>([]);
|
||||
|
||||
const [selectedGroupList, setSelectedGroupList] = useState<MemberGroupListItemType<false>[]>([]);
|
||||
const roleList = useContextSelector(CollaboratorContext, (v) => v.roleList);
|
||||
const getRoleLabelList = useContextSelector(CollaboratorContext, (v) => v.getRoleLabelList);
|
||||
const [selectedRole, setSelectedRole] = useState<number | undefined>(roleList?.read?.value);
|
||||
const roleLabel = useMemo(() => {
|
||||
if (selectedRole === undefined) return '';
|
||||
return getRoleLabelList(selectedRole!).join('、');
|
||||
}, [getRoleLabelList, selectedRole]);
|
||||
useEffect(() => {
|
||||
setCollaboratorList(collaboratorDetailList);
|
||||
}, [collaboratorDetailList]);
|
||||
|
||||
const onUpdateCollaborators = useContextSelector(
|
||||
CollaboratorContext,
|
||||
(v) => v.onUpdateCollaborators
|
||||
);
|
||||
|
||||
const { runAsync: onConfirm, loading: isUpdating } = useRequest2(
|
||||
const parentClbs = useContextSelector(CollaboratorContext, (v) => v.parentClbList);
|
||||
const myRole = useContextSelector(CollaboratorContext, (v) => v.myRole);
|
||||
|
||||
const { runAsync: _onConfirm, loading: isUpdating } = useRequest2(
|
||||
() =>
|
||||
onUpdateCollaborators({
|
||||
members: selectedMemberList.map((item) => item.tmbId),
|
||||
groups: selectedGroupList.map((item) => item._id),
|
||||
orgs: selectedOrgList.map((item) => item._id),
|
||||
permission: addOnly ? undefined : selectedRole!
|
||||
} as UpdateClbPermissionProps<ValueOf<typeof addOnly>>),
|
||||
collaborators: editCollaborators.map(
|
||||
(clb) =>
|
||||
({
|
||||
tmbId: clb.tmbId,
|
||||
groupId: clb.groupId,
|
||||
orgId: clb.orgId,
|
||||
permission: clb.permission.role
|
||||
}) as CollaboratorItemType
|
||||
)
|
||||
}),
|
||||
{
|
||||
successToast: t('common:add_success'),
|
||||
onSuccess() {
|
||||
|
|
@ -127,334 +122,410 @@ function MemberModal({
|
|||
}
|
||||
);
|
||||
|
||||
const { openConfirm: openConfirmDisableInheritPer, ConfirmModal: ConfirmDisableInheritPer } =
|
||||
useConfirm({
|
||||
content: t('common:permission.Remove InheritPermission Confirm')
|
||||
});
|
||||
|
||||
const onConfirm = useCallback(() => {
|
||||
const _parentClbs = parentClbs.map((clb) => ({
|
||||
...clb,
|
||||
permission: clb.permission.role === OwnerRoleVal ? ManageRoleVal : clb.permission.role
|
||||
}));
|
||||
|
||||
const newChildClbs = editCollaborators.map((clb) => ({
|
||||
...clb,
|
||||
permission: clb.permission.role
|
||||
}));
|
||||
|
||||
const isConflict = checkRoleUpdateConflict({
|
||||
parentClbs: _parentClbs,
|
||||
newChildClbs
|
||||
});
|
||||
if (isConflict && isInheritPermission) {
|
||||
return openConfirmDisableInheritPer(_onConfirm)();
|
||||
} else {
|
||||
return _onConfirm();
|
||||
}
|
||||
}, [
|
||||
_onConfirm,
|
||||
editCollaborators,
|
||||
isInheritPermission,
|
||||
openConfirmDisableInheritPer,
|
||||
parentClbs
|
||||
]);
|
||||
|
||||
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(() => {
|
||||
return [
|
||||
...selectedOrgList.map((item) => ({
|
||||
id: `org-${item._id}`,
|
||||
avatar: item.avatar,
|
||||
name: item.name,
|
||||
onDelete: () => setSelectedOrgIdList(selectedOrgList.filter((v) => v._id !== item._id)),
|
||||
orgs: undefined
|
||||
})),
|
||||
...selectedGroupList.map((item) => ({
|
||||
id: `group-${item._id}`,
|
||||
avatar: item.avatar,
|
||||
name: item.name === DefaultGroupName ? userInfo?.team.teamName : item.name,
|
||||
onDelete: () => setSelectedGroupList(selectedGroupList.filter((v) => v._id !== item._id)),
|
||||
orgs: undefined
|
||||
})),
|
||||
...selectedMemberList.map((item) => ({
|
||||
id: `member-${item.tmbId}`,
|
||||
avatar: item.avatar,
|
||||
name: item.memberName,
|
||||
onDelete: () =>
|
||||
setSelectedMemberList(selectedMemberList.filter((v) => v.tmbId !== item.tmbId)),
|
||||
orgs: item.orgs
|
||||
}))
|
||||
];
|
||||
}, [selectedOrgList, selectedGroupList, selectedMemberList, userInfo?.team.teamName]);
|
||||
const memberWithPer = useMemo(() => {
|
||||
const map = new Map(collaboratorDetailList.map((clb) => [getCollaboratorId(clb), { ...clb }]));
|
||||
return members.map((member) => {
|
||||
const clb = map.get(getCollaboratorId(member));
|
||||
return {
|
||||
...member,
|
||||
permission: new Permission({
|
||||
role: clb?.permission.role
|
||||
})
|
||||
};
|
||||
});
|
||||
}, [collaboratorDetailList, members]);
|
||||
|
||||
const orgMembersWithPer = useMemo(() => {
|
||||
const map = new Map(collaboratorDetailList.map((clb) => [getCollaboratorId(clb), { ...clb }]));
|
||||
return orgMembers.map((member) => {
|
||||
const clb = map.get(getCollaboratorId(member));
|
||||
return {
|
||||
...member,
|
||||
permission: new Permission({
|
||||
role: clb?.permission.role
|
||||
})
|
||||
};
|
||||
});
|
||||
}, [collaboratorDetailList, orgMembers]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc={addOnly ? 'keyPrimary' : 'modal/AddClb'}
|
||||
title={addOnly ? t('user:team.add_permission') : t('user:team.add_collaborator')}
|
||||
minW="800px"
|
||||
maxW={'60vw'}
|
||||
h={'100%'}
|
||||
maxH={'90vh'}
|
||||
isCentered
|
||||
isLoading={loadingGroupsAndOrgs}
|
||||
>
|
||||
<ModalBody flex={'1'}>
|
||||
<Grid
|
||||
border="1px solid"
|
||||
borderColor="myGray.200"
|
||||
borderRadius="0.5rem"
|
||||
gridTemplateColumns="1fr 1fr"
|
||||
h={'100%'}
|
||||
>
|
||||
<Flex
|
||||
h={'100%'}
|
||||
flexDirection="column"
|
||||
borderRight="1px solid"
|
||||
<>
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc={'common/settingLight'}
|
||||
title={t('user:team.manage_collaborators')}
|
||||
minW="900px"
|
||||
maxW={'60vw'}
|
||||
h={'100%'}
|
||||
isCentered
|
||||
isLoading={loadingGroupsAndOrgs}
|
||||
>
|
||||
<ModalBody flex={'1'}>
|
||||
<Grid
|
||||
border="1px solid"
|
||||
borderColor="myGray.200"
|
||||
p="4"
|
||||
borderRadius="0.5rem"
|
||||
gridTemplateColumns="40% 60%"
|
||||
h={'100%'}
|
||||
>
|
||||
<SearchInput
|
||||
placeholder={t('user:search_group_org_user')}
|
||||
bgColor="myGray.50"
|
||||
onChange={(e) => setSearchKey(e.target.value)}
|
||||
/>
|
||||
<Flex
|
||||
h={'100%'}
|
||||
flexDirection="column"
|
||||
borderRight="1px solid"
|
||||
borderColor="myGray.200"
|
||||
py="2"
|
||||
>
|
||||
<Box px={2}>
|
||||
<SearchInput
|
||||
placeholder={t('user:search_group_org_user')}
|
||||
bgColor="myGray.50"
|
||||
onChange={(e) => setSearchKey(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Flex flexDirection="column" mt="3" overflow={'auto'} flex={'1 0 0'} h={0}>
|
||||
{/* Entry */}
|
||||
{!searchKey && !filterClass && (
|
||||
<>
|
||||
{entryList.current.map((item) => {
|
||||
return (
|
||||
<HStack
|
||||
key={item.value}
|
||||
justifyContent="space-between"
|
||||
py="2"
|
||||
px="3"
|
||||
borderRadius="sm"
|
||||
alignItems="center"
|
||||
_hover={HoverBoxStyle}
|
||||
_notLast={{ mb: 1 }}
|
||||
onClick={() => setFilterClass(item.value as any)}
|
||||
<Flex flexDirection="column" mt="3" overflow={'auto'} flex={'1 0 0'} h={0}>
|
||||
{/* Entry */}
|
||||
{!searchKey && !filterClass && (
|
||||
<Box px={2}>
|
||||
{entryList.current.map((item) => {
|
||||
return (
|
||||
<HStack
|
||||
key={item.value}
|
||||
justifyContent="space-between"
|
||||
py="2"
|
||||
px="3"
|
||||
borderRadius="sm"
|
||||
alignItems="center"
|
||||
_hover={HoverBoxStyle}
|
||||
_notLast={{ mb: 1 }}
|
||||
onClick={() => setFilterClass(item.value as any)}
|
||||
>
|
||||
<MyAvatar src={item.icon} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box ml="2" w="full">
|
||||
{item.label}
|
||||
</Box>
|
||||
<MyIcon name="core/chat/chevronRight" w="16px" />
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Path */}
|
||||
{!searchKey && filterClass && (
|
||||
<Box mb={1} px={2}>
|
||||
<Path
|
||||
paths={[
|
||||
{
|
||||
parentId: filterClass,
|
||||
parentName:
|
||||
filterClass === 'member'
|
||||
? t('user:team.group.members')
|
||||
: filterClass === 'org'
|
||||
? t('user:team.org.org')
|
||||
: t('user:team.group.group')
|
||||
},
|
||||
...paths
|
||||
]}
|
||||
onClick={(parentId) => {
|
||||
if (parentId === '') {
|
||||
setFilterClass(undefined);
|
||||
onPathClick('');
|
||||
} else if (
|
||||
parentId === 'member' ||
|
||||
parentId === 'org' ||
|
||||
parentId === 'group'
|
||||
) {
|
||||
setFilterClass(parentId);
|
||||
onPathClick('');
|
||||
} else {
|
||||
onPathClick(parentId);
|
||||
}
|
||||
}}
|
||||
rootName={t('common:Team')}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{(filterClass === 'member' || searchKey) &&
|
||||
(() => {
|
||||
const MemberList = (
|
||||
<RenderMemberList
|
||||
members={memberWithPer}
|
||||
setCollaboratorList={setCollaboratorList}
|
||||
editCollaborators={editCollaborators}
|
||||
defaultRole={defaultRole}
|
||||
/>
|
||||
);
|
||||
return searchKey ? (
|
||||
<Box px={2}>{MemberList}</Box>
|
||||
) : (
|
||||
<TeamMemberScrollData
|
||||
flexDirection={'column'}
|
||||
gap={1}
|
||||
userSelect={'none'}
|
||||
height={'fit-content'}
|
||||
px={2}
|
||||
>
|
||||
<MyAvatar src={item.icon} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box ml="2" w="full">
|
||||
{item.label}
|
||||
</Box>
|
||||
<MyIcon name="core/chat/chevronRight" w="16px" />
|
||||
</HStack>
|
||||
{MemberList}
|
||||
</TeamMemberScrollData>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
})()}
|
||||
{(filterClass === 'org' || searchKey) &&
|
||||
(() => {
|
||||
const Orgs = orgs?.map((org) => {
|
||||
const addTheOrg = () => {
|
||||
setCollaboratorList((state) => {
|
||||
if (state.find((v) => v.orgId === org._id)) {
|
||||
return state.filter((v) => v.orgId !== org._id);
|
||||
}
|
||||
return [
|
||||
...state,
|
||||
{
|
||||
...org,
|
||||
orgId: org._id,
|
||||
permission: new Permission({ role: defaultRole })
|
||||
}
|
||||
];
|
||||
});
|
||||
};
|
||||
const isChecked = !!editCollaborators.find((v) => v.orgId === org._id);
|
||||
return (
|
||||
<MemberItemCard
|
||||
avatar={org.avatar}
|
||||
key={org._id}
|
||||
name={org.name}
|
||||
onChange={addTheOrg}
|
||||
isChecked={isChecked}
|
||||
rightSlot={
|
||||
org.total && (
|
||||
<MyIcon
|
||||
name="core/chat/chevronRight"
|
||||
w="16px"
|
||||
p="4px"
|
||||
rounded={'6px'}
|
||||
_hover={{
|
||||
bgColor: 'myGray.200'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
onClickOrg(org);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return searchKey ? (
|
||||
<Box px={2}>{Orgs}</Box>
|
||||
) : (
|
||||
<OrgMemberScrollData px={2}>
|
||||
{Orgs}
|
||||
<RenderMemberList
|
||||
members={orgMembersWithPer}
|
||||
setCollaboratorList={setCollaboratorList}
|
||||
editCollaborators={editCollaborators}
|
||||
defaultRole={defaultRole}
|
||||
/>
|
||||
</OrgMemberScrollData>
|
||||
);
|
||||
})()}
|
||||
{(filterClass === 'group' || searchKey) && (
|
||||
<Box
|
||||
{...(searchKey
|
||||
? {}
|
||||
: {
|
||||
flex: '1 0 0',
|
||||
overflow: 'auto'
|
||||
})}
|
||||
px={2}
|
||||
>
|
||||
{groups?.map((group) => {
|
||||
const addGroup = () => {
|
||||
setCollaboratorList((state) => {
|
||||
if (state.find((v) => v.groupId === group._id)) {
|
||||
return state.filter((v) => v.groupId !== group._id);
|
||||
}
|
||||
return [
|
||||
...state,
|
||||
{
|
||||
...group,
|
||||
groupId: group._id,
|
||||
permission: new Permission({ role: defaultRole })
|
||||
}
|
||||
];
|
||||
});
|
||||
};
|
||||
const isChecked = !!editCollaborators.find((v) => v.groupId === group._id);
|
||||
return (
|
||||
<MemberItemCard
|
||||
avatar={group.avatar}
|
||||
key={group._id}
|
||||
name={
|
||||
group.name === DefaultGroupName
|
||||
? userInfo?.team.teamName ?? ''
|
||||
: group.name
|
||||
}
|
||||
onChange={addGroup}
|
||||
isChecked={isChecked}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{/* Path */}
|
||||
{!searchKey && filterClass && (
|
||||
<Box mb={1}>
|
||||
<Path
|
||||
paths={[
|
||||
{
|
||||
parentId: filterClass,
|
||||
parentName:
|
||||
filterClass === 'member'
|
||||
? t('user:team.group.members')
|
||||
: filterClass === 'org'
|
||||
? t('user:team.org.org')
|
||||
: t('user:team.group.group')
|
||||
},
|
||||
...paths
|
||||
]}
|
||||
onClick={(parentId) => {
|
||||
if (parentId === '') {
|
||||
setFilterClass(undefined);
|
||||
onPathClick('');
|
||||
} else if (
|
||||
parentId === 'member' ||
|
||||
parentId === 'org' ||
|
||||
parentId === 'group'
|
||||
) {
|
||||
setFilterClass(parentId);
|
||||
onPathClick('');
|
||||
} else {
|
||||
onPathClick(parentId);
|
||||
}
|
||||
}}
|
||||
rootName={t('common:Team')}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{(filterClass === 'member' || searchKey) &&
|
||||
(() => {
|
||||
const Members = members?.map((member) => {
|
||||
const onChange = () => {
|
||||
setSelectedMemberList((state) => {
|
||||
if (state.find((v) => v.tmbId === member.tmbId)) {
|
||||
return state.filter((v) => v.tmbId !== member.tmbId);
|
||||
}
|
||||
return [...state, member];
|
||||
});
|
||||
};
|
||||
const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId);
|
||||
return (
|
||||
<MemberItemCard
|
||||
addOnly={addOnly}
|
||||
avatar={member.avatar}
|
||||
key={member.tmbId}
|
||||
name={member.memberName}
|
||||
role={collaborator?.permission.role}
|
||||
onChange={onChange}
|
||||
isChecked={!!selectedMemberList.find((v) => v.tmbId === member.tmbId)}
|
||||
orgs={member.orgs}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return searchKey ? (
|
||||
Members
|
||||
) : (
|
||||
<TeamMemberScrollData
|
||||
flexDirection={'column'}
|
||||
gap={1}
|
||||
userSelect={'none'}
|
||||
height={'fit-content'}
|
||||
>
|
||||
{Members}
|
||||
</TeamMemberScrollData>
|
||||
);
|
||||
})()}
|
||||
{(filterClass === 'org' || searchKey) &&
|
||||
(() => {
|
||||
const Orgs = orgs?.map((org) => {
|
||||
const onChange = () => {
|
||||
setSelectedOrgIdList((state) => {
|
||||
if (state.find((v) => v._id === org._id)) {
|
||||
return state.filter((v) => v._id !== org._id);
|
||||
}
|
||||
return [...state, org];
|
||||
});
|
||||
};
|
||||
const collaborator = collaboratorList?.find((v) => v.orgId === org._id);
|
||||
return (
|
||||
<MemberItemCard
|
||||
avatar={org.avatar}
|
||||
key={org._id}
|
||||
name={org.name}
|
||||
onChange={onChange}
|
||||
addOnly={addOnly}
|
||||
role={collaborator?.permission.role}
|
||||
isChecked={!!selectedOrgList.find((v) => String(v._id) === String(org._id))}
|
||||
rightSlot={
|
||||
org.total && (
|
||||
<MyIcon
|
||||
name="core/chat/chevronRight"
|
||||
w="16px"
|
||||
p="4px"
|
||||
rounded={'6px'}
|
||||
_hover={{
|
||||
bgColor: 'myGray.200'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
onClickOrg(org);
|
||||
// setPath(getOrgChildrenPath(org));
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return searchKey ? (
|
||||
Orgs
|
||||
) : (
|
||||
<OrgMemberScrollData>
|
||||
{Orgs}
|
||||
{orgMembers.map((member) => {
|
||||
const isChecked = !!selectedMemberList.find(
|
||||
(v) => v.tmbId === member.tmbId
|
||||
);
|
||||
const collaborator = collaboratorList?.find(
|
||||
(v) => v.tmbId === member.tmbId
|
||||
);
|
||||
return (
|
||||
<MemberItemCard
|
||||
avatar={member.avatar}
|
||||
key={member.tmbId}
|
||||
name={member.memberName}
|
||||
onChange={() => {
|
||||
setSelectedMemberList((state) => {
|
||||
if (state.find((v) => v.tmbId === member.tmbId)) {
|
||||
return state.filter((v) => v.tmbId !== member.tmbId);
|
||||
}
|
||||
return [...state, member];
|
||||
});
|
||||
}}
|
||||
isChecked={isChecked}
|
||||
role={collaborator?.permission.role}
|
||||
addOnly={addOnly && !!member.permission.role}
|
||||
orgs={member.orgs}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</OrgMemberScrollData>
|
||||
);
|
||||
})()}
|
||||
{(filterClass === 'group' || searchKey) &&
|
||||
groups?.map((group) => {
|
||||
const onChange = () => {
|
||||
setSelectedGroupList((state) => {
|
||||
if (state.find((v) => v._id === group._id)) {
|
||||
return state.filter((v) => v._id !== group._id);
|
||||
}
|
||||
return [...state, group];
|
||||
<Flex h={'100%'} flexDirection="column" overflow={'auto'} p="2">
|
||||
<Box mt={2} mb={3}>{`${t('common:chosen')}: ${editCollaborators.length}`}</Box>
|
||||
<Flex flexDirection="column" gap={1} flex={'1 0 0'} h={0}>
|
||||
{editCollaborators.map((clb) => {
|
||||
const onDelete = () => {
|
||||
setCollaboratorList((state) => {
|
||||
return state.filter((v) => getCollaboratorId(v) !== getCollaboratorId(clb));
|
||||
});
|
||||
};
|
||||
const onRoleChange = (role: RoleValueType) => {
|
||||
setCollaboratorList((state) => {
|
||||
const index = state.findIndex(
|
||||
(v) => getCollaboratorId(v) === getCollaboratorId(clb)
|
||||
);
|
||||
if (index === -1) return state;
|
||||
return [
|
||||
...state.slice(0, index),
|
||||
{
|
||||
...state[index],
|
||||
permission: new Permission({ role })
|
||||
},
|
||||
...state.slice(index + 1)
|
||||
];
|
||||
});
|
||||
};
|
||||
const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
|
||||
return (
|
||||
<MemberItemCard
|
||||
avatar={group.avatar}
|
||||
key={group._id}
|
||||
name={
|
||||
group.name === DefaultGroupName ? userInfo?.team.teamName ?? '' : group.name
|
||||
key={'chosen-' + getCollaboratorId(clb)}
|
||||
avatar={clb.avatar}
|
||||
name={clb.name ?? ''}
|
||||
onDelete={onDelete}
|
||||
role={clb.permission.role}
|
||||
onRoleChange={onRoleChange}
|
||||
disabled={
|
||||
clb.permission.role === OwnerRoleVal ||
|
||||
clb.tmbId === userInfo?.team.tmbId ||
|
||||
(clb.permission.hasManagePer && !myRole.isOwner)
|
||||
}
|
||||
role={collaborator?.permission.role}
|
||||
onChange={onChange}
|
||||
isChecked={!!selectedGroupList.find((v) => v._id === group._id)}
|
||||
addOnly={addOnly}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex h={'100%'} p="4" flexDirection="column">
|
||||
<Box>
|
||||
{`${t('user:has_chosen')}: `}
|
||||
{selectedMemberList.length + selectedGroupList.length + selectedOrgList.length}
|
||||
</Box>
|
||||
<Flex flexDirection="column" mt="2" gap={1} overflow={'auto'} flex={'1 0 0'} h={0}>
|
||||
{selectedList.map((item) => {
|
||||
return (
|
||||
<MemberItemCard
|
||||
key={item.id}
|
||||
avatar={item.avatar}
|
||||
name={item.name ?? ''}
|
||||
onChange={item.onDelete}
|
||||
onDelete={item.onDelete}
|
||||
orgs={item?.orgs}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{!addOnly && !!roleList && (
|
||||
<RoleSelect
|
||||
value={selectedRole}
|
||||
Button={
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
bg={'myGray.50'}
|
||||
border="base"
|
||||
fontSize={'sm'}
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
h={'32px'}
|
||||
>
|
||||
{roleLabel}
|
||||
<ChevronDownIcon fontSize={'md'} />
|
||||
</Flex>
|
||||
}
|
||||
onChange={(v) => setSelectedRole(v)}
|
||||
/>
|
||||
)}
|
||||
{addOnly && (
|
||||
<HStack bg={'blue.50'} color={'blue.600'} padding={'6px 12px'} rounded={'5px'}>
|
||||
<MyIcon name="common/info" w="1rem" h="1rem" />
|
||||
<Text fontSize="12px">{t('user:permission_add_tip')}</Text>
|
||||
</HStack>
|
||||
)}
|
||||
<Button isLoading={isUpdating} ml="4" h={'32px'} onClick={onConfirm}>
|
||||
{t('common:Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
</Flex>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button isLoading={isUpdating} ml="4" h={'32px'} onClick={onConfirm}>
|
||||
{t('common:Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
<ConfirmDisableInheritPer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MemberModal;
|
||||
|
||||
const RenderMemberList = ({
|
||||
members,
|
||||
setCollaboratorList,
|
||||
editCollaborators,
|
||||
defaultRole
|
||||
}: {
|
||||
members: Array<Omit<TeamMemberItemType, 'permission'> & { permission: Permission }>;
|
||||
setCollaboratorList: React.Dispatch<React.SetStateAction<CollaboratorItemDetailType[]>>;
|
||||
editCollaborators: CollaboratorItemDetailType[];
|
||||
defaultRole: RoleValueType;
|
||||
}) => {
|
||||
const { userInfo } = useUserStore();
|
||||
const myRole = useContextSelector(CollaboratorContext, (v) => v.myRole);
|
||||
|
||||
return (
|
||||
<>
|
||||
{members?.map((member) => {
|
||||
const addTheMember = () => {
|
||||
setCollaboratorList((state) => {
|
||||
if (state.find((v) => v.tmbId === member.tmbId)) {
|
||||
return state.filter((v) => v.tmbId !== member.tmbId);
|
||||
}
|
||||
return [
|
||||
...state,
|
||||
{
|
||||
tmbId: member.tmbId,
|
||||
avatar: member.avatar,
|
||||
name: member.memberName,
|
||||
teamId: member.teamId,
|
||||
permission: new Permission({ role: defaultRole })
|
||||
}
|
||||
];
|
||||
});
|
||||
};
|
||||
const isChecked = !!editCollaborators.find((v) => v.tmbId === member.tmbId);
|
||||
return (
|
||||
<MemberItemCard
|
||||
role={member.permission.role}
|
||||
avatar={member.avatar}
|
||||
key={member.tmbId}
|
||||
name={member.memberName}
|
||||
onChange={addTheMember}
|
||||
isChecked={isChecked}
|
||||
orgs={member.orgs}
|
||||
disabled={
|
||||
member.permission.role === OwnerRoleVal ||
|
||||
member.tmbId === userInfo?.team.tmbId ||
|
||||
(member.permission.hasManagePer && !myRole.isOwner)
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { Permission } from '@fastgpt/global/support/permission/controller';
|
|||
import { CollaboratorContext } from './context';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyDivider from '@fastgpt/web/components/common/MyDivider';
|
||||
import { ManageRoleVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export type PermissionSelectProps = {
|
||||
value?: RoleValueType;
|
||||
|
|
@ -47,16 +48,15 @@ function RoleSelect({
|
|||
offset = [0, 5],
|
||||
Button,
|
||||
width = 'auto',
|
||||
onDelete
|
||||
onDelete,
|
||||
disabled
|
||||
}: PermissionSelectProps) {
|
||||
const { t } = useTranslation();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const closeTimer = useRef<NodeJS.Timeout>();
|
||||
|
||||
const { permission, roleList: permissionList } = useContextSelector(
|
||||
CollaboratorContext,
|
||||
(v) => v
|
||||
);
|
||||
const { roleList: permissionList } = useContextSelector(CollaboratorContext, (v) => v);
|
||||
const myRole = useContextSelector(CollaboratorContext, (v) => v.myRole);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
|
|
@ -72,15 +72,18 @@ function RoleSelect({
|
|||
};
|
||||
});
|
||||
|
||||
const singleOptions = list.filter((item) => item.checkBoxType === 'single');
|
||||
const per = new Permission({ role });
|
||||
|
||||
return {
|
||||
singleOptions: list.filter(
|
||||
(item) =>
|
||||
item.checkBoxType === 'single' &&
|
||||
(permission.isOwner || item.value !== permissionList['manage'].value)
|
||||
),
|
||||
singleOptions: myRole.isOwner
|
||||
? singleOptions
|
||||
: myRole.hasManagePer && !per.hasManagePer
|
||||
? singleOptions.filter((item) => item.value !== ManageRoleVal)
|
||||
: [],
|
||||
checkboxList: list.filter((item) => item.checkBoxType === 'multiple')
|
||||
};
|
||||
}, [permission.isOwner, permissionList]);
|
||||
}, [myRole.hasManagePer, myRole.isOwner, permissionList, role]);
|
||||
const selectedSingleValue = useMemo(() => {
|
||||
if (!permissionList) return undefined;
|
||||
|
||||
|
|
@ -120,6 +123,7 @@ function RoleSelect({
|
|||
ref={ref}
|
||||
w="fit-content"
|
||||
onMouseEnter={() => {
|
||||
if (disabled) return;
|
||||
if (trigger === 'hover') {
|
||||
setIsOpen(true);
|
||||
}
|
||||
|
|
@ -135,8 +139,10 @@ function RoleSelect({
|
|||
>
|
||||
<MenuButton
|
||||
position={'relative'}
|
||||
cursor={disabled ? 'not-allowed' : 'pointer'}
|
||||
onClickCapture={() => {
|
||||
if (trigger === 'click') {
|
||||
if (disabled) return;
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
}}
|
||||
|
|
@ -158,10 +164,10 @@ function RoleSelect({
|
|||
{/* The list of single select permissions */}
|
||||
{roleOptions.singleOptions.map((item) => {
|
||||
const change = () => {
|
||||
const per = new Permission({ role });
|
||||
per.removeRole(selectedSingleValue);
|
||||
per.addRole(item.value);
|
||||
onSelectRole(per.role);
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
onSelectRole(item.value);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -187,7 +193,7 @@ function RoleSelect({
|
|||
);
|
||||
})}
|
||||
|
||||
{roleOptions.checkboxList.length > 0 && (
|
||||
{roleOptions.checkboxList.length > 0 && roleOptions.singleOptions.length > 0 && (
|
||||
<>
|
||||
<MyDivider />
|
||||
<Box pb="2" px="3" fontSize={'sm'} color={'myGray.900'}>
|
||||
|
|
@ -197,7 +203,8 @@ function RoleSelect({
|
|||
)}
|
||||
|
||||
{roleOptions.checkboxList.map((item) => {
|
||||
const change = () => {
|
||||
const change = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
if ((e.target as HTMLElement).tagName === 'INPUT') return;
|
||||
const per = new Permission({ role });
|
||||
if (per.checkRole(item.value)) {
|
||||
per.removeRole(item.value);
|
||||
|
|
@ -216,9 +223,13 @@ function RoleSelect({
|
|||
}
|
||||
: {})}
|
||||
{...MenuStyle}
|
||||
onClick={(e) => {
|
||||
if (disabled) return;
|
||||
change(e);
|
||||
}}
|
||||
>
|
||||
<Checkbox size="sm" isChecked={isChecked} onChange={change} />
|
||||
<Flex ml={4} flexDirection="column" flex={'1 0 0'} onClick={change}>
|
||||
<Checkbox size="sm" isChecked={isChecked} />
|
||||
<Flex ml={4} flexDirection="column" flex={'1 0 0'}>
|
||||
<Box>{t(item.name as any)}</Box>
|
||||
<Box color={'myGray.500'} fontSize={'mini'}>
|
||||
{t(item.description as any)}
|
||||
|
|
|
|||
|
|
@ -19,16 +19,16 @@ function RoleTags({ permission }: PermissionTagsProp) {
|
|||
const roleTagList = getRoleLabelList(permission);
|
||||
|
||||
return (
|
||||
<Flex gap="2" alignItems="center">
|
||||
<Flex gap="2" alignItems="center" flexWrap="wrap" minH="15px">
|
||||
{roleTagList.map((item) => (
|
||||
<Tag
|
||||
mixBlendMode={'multiply'}
|
||||
key={item}
|
||||
colorSchema="blue"
|
||||
border="none"
|
||||
py={2}
|
||||
px={3}
|
||||
fontSize={'xs'}
|
||||
py={1}
|
||||
px={2}
|
||||
fontSize={'2xs'}
|
||||
>
|
||||
{t(item as any)}
|
||||
</Tag>
|
||||
|
|
|
|||