diff --git a/docSite/content/zh-cn/docs/development/upgrading/4818.md b/docSite/content/zh-cn/docs/development/upgrading/4818.md index b87c83fd409c..e6665959cb1b 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/4818.md +++ b/docSite/content/zh-cn/docs/development/upgrading/4818.md @@ -10,10 +10,13 @@ weight: 806 ## 完整更新内容 1. -2. 新增 - 支持部门架构权限模式 -3. 优化 - 图片上传安全校验。并增加头像图片唯一存储,确保不会累计存储。 -4. 优化 - Mongo 全文索引表分离。 -5. 优化 - 知识库检索查询语句合并,同时减少查库数量。 -6. 优化 - 文件编码检测,减少 CSV 文件乱码概率。 -7. 优化 - 异步读取文件内容,减少进程阻塞。 -8. 修复 - HTML 文件上传,base64 图片无法自动转图片链接。 \ No newline at end of file +2. 新增 - 支持部门架构权限模式。 +3. 新增 - 支持配置自定跨域安全策略,默认全开。 +4. 优化 - 图片上传安全校验。并增加头像图片唯一存储,确保不会累计存储。 +5. 优化 - Mongo 全文索引表分离。 +6. 优化 - 知识库检索查询语句合并,同时减少查库数量。 +7. 优化 - 文件编码检测,减少 CSV 文件乱码概率。 +8. 优化 - 异步读取文件内容,减少进程阻塞。 +9. 优化 - 文件阅读,HTML 直接下载,不允许在线阅读。 +10. 修复 - HTML 文件上传,base64 图片无法自动转图片链接。 +11. 修复 - 插件计费错误。 \ No newline at end of file diff --git a/packages/global/core/ai/model.d.ts b/packages/global/core/ai/model.d.ts index 83f13eb812aa..533ced31d88a 100644 --- a/packages/global/core/ai/model.d.ts +++ b/packages/global/core/ai/model.d.ts @@ -53,6 +53,7 @@ export type VectorModelItemType = PriceType & { }; export type ReRankModelItemType = PriceType & { + provider: ModelProviderIdType; model: string; name: string; requestUrl: string; diff --git a/packages/global/support/permission/collaborator.d.ts b/packages/global/support/permission/collaborator.d.ts index faa1d3948618..af7ee84e1a03 100644 --- a/packages/global/support/permission/collaborator.d.ts +++ b/packages/global/support/permission/collaborator.d.ts @@ -20,40 +20,8 @@ export type UpdateClbPermissionProps = { permission: PermissionValueType; }; -export type DeleteClbPermissionProps = RequireOnlyOne<{ - tmbId: string; - groupId: string; - orgId: string; -}>; - -export type UpdatePermissionBody = { - permission: PermissionValueType; -} & RequireOnlyOne<{ - memberId: string; - groupId: string; - orgId: string; -}>; - -export type CreatePermissionBody = { - tmbId: string[]; - groupId: string[]; - orgId: string[]; -}; - export type DeletePermissionQuery = RequireOnlyOne<{ tmbId?: string; groupId?: string; orgId?: string; }>; - -export type TeamClbsListType = { - permission: number; - name: string; - avatar: string; -}; - -export type ListPermissionResponse = { - tmb: (TeamClbsListType & { tmbId: string })[]; - group: (TeamClbsListType & { groupId: string })[]; - org: (TeamClbsListType & { orgId: string })[]; -}; diff --git a/packages/service/core/ai/config/embedding/text-embedding-ada-002.json b/packages/service/core/ai/config/embedding/text-embedding-ada-002.json new file mode 100644 index 000000000000..4f6290f7549f --- /dev/null +++ b/packages/service/core/ai/config/embedding/text-embedding-ada-002.json @@ -0,0 +1,11 @@ +{ + "provider": "OpenAI", + "model": "text-embedding-ada-002", + "name": "text-embedding-ada-002", + + "defaultToken": 512, // 默认分块 token + "maxToken": 3000, // 最大分块 token + "weight": 0, // 权重 + + "charsPointsPrice": 0 // 积分/1k token +} diff --git a/packages/service/core/ai/config/llm/gpt-4o-mini.json b/packages/service/core/ai/config/llm/gpt-4o-mini.json new file mode 100644 index 000000000000..0bcf7f6defc2 --- /dev/null +++ b/packages/service/core/ai/config/llm/gpt-4o-mini.json @@ -0,0 +1,33 @@ +{ + "provider": "OpenAI", + "model": "gpt-4o-mini", + "name": "GPT-4o-mini", // alias + + "maxContext": 125000, // 最大上下文 + "maxResponse": 16000, // 最大回复 + "quoteMaxToken": 60000, // 最大引用 + "maxTemperature": 1.2, // 最大温度 + "presencePenaltyRange": [-2, 2], // 惩罚系数范围 + "frequencyPenaltyRange": [-2, 2], // 频率惩罚系数范围 + "responseFormatList": ["text", "json_object", "json_schema"], // 响应格式 + "showStopSign": true, // 是否显示停止符号 + + "vision": true, // 是否支持图片识别 + "toolChoice": true, // 是否支持工具调用 + "functionCall": false, // 是否支持函数调用(一般都可以 false 了,基本不用了) + "defaultSystemChatPrompt": "", // 默认系统提示 + + "datasetProcess": true, // 用于知识库文本处理 + "usedInClassify": true, // 用于问题分类 + "customCQPrompt": "", // 自定义问题分类提示 + "usedInExtractFields": true, // 用于提取字段 + "customExtractPrompt": "", // 自定义提取提示 + "usedInToolCall": true, // 用于工具调用 + "usedInQueryExtension": true, // 用于问题优化 + + "defaultConfig": {}, // 额外的自定义 body + "fieldMap": {}, // body 字段映射 + + "censor": false, // 是否开启敏感词过滤 + "charsPointsPrice": 0 // n 积分/1k token +} diff --git a/packages/service/core/ai/config/rerank/bge-reranker-v2-m3.json b/packages/service/core/ai/config/rerank/bge-reranker-v2-m3.json new file mode 100644 index 000000000000..3cc1a33b5a42 --- /dev/null +++ b/packages/service/core/ai/config/rerank/bge-reranker-v2-m3.json @@ -0,0 +1,6 @@ +{ + "provider": "BAAI", + "model": "bge-reranker-v2-m3", + "name": "bge-reranker-v2-m3", + "charsPointsPrice": 0 +} diff --git a/packages/service/core/ai/config/stt/whisper-1.json b/packages/service/core/ai/config/stt/whisper-1.json new file mode 100644 index 000000000000..2d8639786266 --- /dev/null +++ b/packages/service/core/ai/config/stt/whisper-1.json @@ -0,0 +1,6 @@ +{ + "provider": "OpenAI", + "model": "whisper-1", + "name": "whisper-1", + "charsPointsPrice": 0 +} diff --git a/packages/service/core/ai/config/tts/tts-1.json b/packages/service/core/ai/config/tts/tts-1.json new file mode 100644 index 000000000000..80105c227767 --- /dev/null +++ b/packages/service/core/ai/config/tts/tts-1.json @@ -0,0 +1,32 @@ +{ + "provider": "OpenAI", + "model": "tts-1", + "name": "TTS1", + "charsPointsPrice": 0, + "voices": [ + { + "label": "Alloy", + "value": "alloy" + }, + { + "label": "Echo", + "value": "echo" + }, + { + "label": "Fable", + "value": "fable" + }, + { + "label": "Onyx", + "value": "onyx" + }, + { + "label": "Nova", + "value": "nova" + }, + { + "label": "Shimmer", + "value": "shimmer" + } + ] +} diff --git a/packages/service/core/app/plugin/utils.ts b/packages/service/core/app/plugin/utils.ts index bd1b764da5a9..43e0db8aae8c 100644 --- a/packages/service/core/app/plugin/utils.ts +++ b/packages/service/core/app/plugin/utils.ts @@ -5,11 +5,11 @@ import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; /* Plugin points calculation: - 1. 商业版插件: + 1. 系统插件/商业版插件: - 有错误:返回 0 - - 无错误:返回 配置的点数 + 子节点点数 - 2. 其他插件: - - 返回 子节点点数 + - 无错误:返回 单次积分 + 子流程积分(可配置) + 2. 个人插件 + - 返回 子流程积分 */ export const computedPluginUsage = async ({ plugin, @@ -26,9 +26,9 @@ export const computedPluginUsage = async ({ if (source !== PluginSourceEnum.personal) { if (error) return 0; - const pluginCurrentCose = plugin.currentCost ?? 0; + const pluginCurrentCost = plugin.currentCost ?? 0; - return plugin.hasTokenFee ? pluginCurrentCose + childrenUsages : pluginCurrentCose; + return plugin.hasTokenFee ? pluginCurrentCost + childrenUsages : pluginCurrentCost; } return childrenUsages; diff --git a/packages/service/support/permission/controller.ts b/packages/service/support/permission/controller.ts index 1b6a0767c827..d944efba1d03 100644 --- a/packages/service/support/permission/controller.ts +++ b/packages/service/support/permission/controller.ts @@ -8,10 +8,7 @@ import { authOpenApiKey } from '../openapi/auth'; import { FileTokenQuery } from '@fastgpt/global/common/file/type'; import { MongoResourcePermission } from './schema'; import { ClientSession } from 'mongoose'; -import { - PermissionValueType, - ResourcePermissionType -} from '@fastgpt/global/support/permission/type'; +import { PermissionValueType } from '@fastgpt/global/support/permission/type'; import { bucketNameMap } from '@fastgpt/global/common/file/constants'; import { addMinutes } from 'date-fns'; import { getGroupsByTmbId } from './memberGroup/controllers'; @@ -107,44 +104,6 @@ export const getResourcePermission = async ({ return concatPer([...groupPers, ...orgPers]); }; -/* 仅取 members 不取 groups */ -export async function getResourceAllClbs({ - resourceId, - teamId, - resourceType, - session -}: { - teamId: string; - session?: ClientSession; -} & ( - | { - resourceType: 'team'; - resourceId?: undefined; - } - | { - resourceType: Omit; - resourceId?: string | null; - } -)): Promise { - return MongoResourcePermission.find( - { - resourceType: resourceType, - teamId: teamId, - resourceId, - groupId: { - $exists: false - }, - orgId: { - $exists: false - } - }, - null, - { - session - } - ).lean(); -} - export async function getResourceClbsAndGroups({ resourceId, resourceType, @@ -172,10 +131,17 @@ export const getClbsAndGroupsWithInfo = async ({ resourceType, teamId }: { - resourceId: ParentIdType; - resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>; teamId: string; -}) => +} & ( + | { + resourceId: ParentIdType; + resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>; + } + | { + resourceType: 'team'; + resourceId?: undefined; + } +)) => Promise.all([ MongoResourcePermission.find({ teamId, diff --git a/packages/web/components/common/Icon/button.tsx b/packages/web/components/common/Icon/button.tsx index 38cd50a39174..9fde10ec46e0 100644 --- a/packages/web/components/common/Icon/button.tsx +++ b/packages/web/components/common/Icon/button.tsx @@ -18,7 +18,6 @@ const MyIconButton = ({ }: Props) => { return ( & { +type Props = Omit & { list: { icon?: string; label: string | React.ReactNode; - value: string; + value: T; }[]; - value: string; - onChange: (e: string) => void; + value: T; + onChange: (e: T) => void; }; const FillRowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props }: Props) => { @@ -61,4 +61,6 @@ const FillRowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props ); }; -export default FillRowTabs; +export default forwardRef(FillRowTabs) as ( + props: Props & { ref?: React.Ref } +) => JSX.Element; diff --git a/packages/web/i18n/en/account.json b/packages/web/i18n/en/account.json index b115fa506652..58155816a401 100644 --- a/packages/web/i18n/en/account.json +++ b/packages/web/i18n/en/account.json @@ -1,7 +1,14 @@ { + "active_model": "Available models", + "add_default_model": "Add a preset model", "api_key": "API key", "bills_and_invoices": "Bills", + "channel": "Channel", "confirm_logout": "Confirm to log out?", + "create_channel": "Add new channel", + "create_model": "Add new model", + "custom_model": "custom model", + "default_model": "Default model", "logout": "Sign out", "model_provider": "Model Provider", "notifications": "Notify", diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index 1dc0b0a58855..f36a3ea8aa3f 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -1187,6 +1187,7 @@ "tag_list": "Tag List", "team_tag": "Team Tag", "textarea_variable_picker_tip": "Enter \"/\" to select a variable", + "unauth_token": "The certificate has expired, please log in again", "unit.character": "Character", "unit.minute": "Minute", "unit.seconds": "Second", diff --git a/packages/web/i18n/en/workflow.json b/packages/web/i18n/en/workflow.json index a76add2cc218..9ee85b9329f0 100644 --- a/packages/web/i18n/en/workflow.json +++ b/packages/web/i18n/en/workflow.json @@ -49,7 +49,7 @@ "execution_error": "Execution Error", "extraction_requirements_description": "Extraction Requirements Description", "extraction_requirements_description_detail": "Provide AI with some background knowledge or requirements to guide it in completing the task better.\\nThis input box can use global variables.", - "extraction_requirements_placeholder": "For example: \\n1. The current time is: {{cTime}}. You are a lab reservation assistant, and your task is to help users reserve a lab by extracting the corresponding reservation information from the text.\\n2. You are a Google search assistant, and you need to extract suitable search terms from the text.", + "extraction_requirements_placeholder": "For example: 1. The current time is: {{cTime}}. \nYou are a laboratory reservation assistant. Your task is to help users make laboratory reservations and obtain the corresponding reservation information from the text.\n\n2. You are the Google Search Assistant and need to extract appropriate search terms from text.", "feedback_text": "Feedback Text", "field_description": "Field Description", "field_description_placeholder": "Describe the function of this input field. If it is a tool call parameter, this description will affect the quality of the model generation.", diff --git a/packages/web/i18n/zh-CN/account.json b/packages/web/i18n/zh-CN/account.json index 85b0e4954865..5eae52e1fb45 100644 --- a/packages/web/i18n/zh-CN/account.json +++ b/packages/web/i18n/zh-CN/account.json @@ -1,7 +1,14 @@ { + "active_model": "可用模型", + "add_default_model": "添加预设模型", "api_key": "API 密钥", "bills_and_invoices": "账单与发票", + "channel": "渠道", "confirm_logout": "确认退出登录?", + "create_channel": "新增渠道", + "create_model": "新增模型", + "custom_model": "自定义模型", + "default_model": "预设模型", "logout": "登出", "model_provider": "模型提供商", "notifications": "通知", diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index 2c8526087387..7aca2c892cd2 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -1190,6 +1190,7 @@ "tag_list": "标签列表", "team_tag": "团队标签", "textarea_variable_picker_tip": "输入\"/\"可选择变量", + "unauth_token": "凭证已过期,请重新登录", "unit.character": "字符", "unit.minute": "分钟", "unit.seconds": "秒", diff --git a/packages/web/i18n/zh-CN/user.json b/packages/web/i18n/zh-CN/user.json index d0ea685aad47..7eb979052736 100644 --- a/packages/web/i18n/zh-CN/user.json +++ b/packages/web/i18n/zh-CN/user.json @@ -106,7 +106,7 @@ "team.group.set_as_admin": "设为管理员", "team.group.toast.can_not_delete_owner": "不能删除所有者, 请先转让", "team.group.transfer_owner": "转让所有者", - "team.org.org": "组织", + "team.org.org": "部门", "team.manage_collaborators": "管理协作者", "team.no_collaborators": "暂无协作者", "team.write_role_member": "可写权限", diff --git a/packages/web/i18n/zh-CN/workflow.json b/packages/web/i18n/zh-CN/workflow.json index 4b7b14ac405a..47696b48dc83 100644 --- a/packages/web/i18n/zh-CN/workflow.json +++ b/packages/web/i18n/zh-CN/workflow.json @@ -49,7 +49,7 @@ "execution_error": "运行错误", "extraction_requirements_description": "提取要求描述", "extraction_requirements_description_detail": "给AI一些对应的背景知识或要求描述,引导AI更好的完成任务。\\n该输入框可使用全局变量。", - "extraction_requirements_placeholder": "例如: \\n1. 当前时间为: {{cTime}}。你是一个实验室预约助手,你的任务是帮助用户预约实验室,从文本中获取对应的预约信息。\\n2. 你是谷歌搜索助手,需要从文本中提取出合适的搜索词。", + "extraction_requirements_placeholder": "例如: 1. 当前时间为: {{cTime}}。你是一个实验室预约助手,你的任务是帮助用户预约实验室,从文本中获取对应的预约信息。\n2. 你是谷歌搜索助手,需要从文本中提取出合适的搜索词。", "feedback_text": "反馈的文本", "field_description": "字段描述", "field_description_placeholder": "描述该输入字段的功能,如果为工具调用参数,则该描述会影响模型生成的质量", diff --git a/packages/web/i18n/zh-Hant/account.json b/packages/web/i18n/zh-Hant/account.json index 395ebe1d2ede..59130d9daf35 100644 --- a/packages/web/i18n/zh-Hant/account.json +++ b/packages/web/i18n/zh-Hant/account.json @@ -1,7 +1,14 @@ { + "active_model": "可用模型", + "add_default_model": "新增預設模型", "api_key": "API 金鑰", "bills_and_invoices": "帳單與發票", + "channel": "頻道", "confirm_logout": "確認登出登入?", + "create_channel": "新增頻道", + "create_model": "新增模型", + "custom_model": "自訂模型", + "default_model": "預設模型", "logout": "登出", "model_provider": "模型提供者", "notifications": "通知", diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index a28aabc59abb..dc3f4d736033 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -1187,6 +1187,7 @@ "tag_list": "標籤列表", "team_tag": "團隊標籤", "textarea_variable_picker_tip": "輸入「/」以選擇變數", + "unauth_token": "憑證已過期,請重新登入", "unit.character": "字元", "unit.minute": "分鐘", "unit.seconds": "秒", diff --git a/packages/web/i18n/zh-Hant/workflow.json b/packages/web/i18n/zh-Hant/workflow.json index d8ec45aedc96..765d16439fe8 100644 --- a/packages/web/i18n/zh-Hant/workflow.json +++ b/packages/web/i18n/zh-Hant/workflow.json @@ -49,7 +49,7 @@ "execution_error": "執行錯誤", "extraction_requirements_description": "擷取需求描述", "extraction_requirements_description_detail": "提供 AI 相對應的背景知識或需求描述,引導 AI 更好地完成任務。\\n這個輸入框可以使用全域變數。", - "extraction_requirements_placeholder": "例如:\\n1. 目前時間為:{{cTime}}。您是一位實驗室預約助理,您的任務是協助使用者預約實驗室,從文字中取得對應的預約資訊。\\n2. 您是 Google 搜尋助理,需要從文字中擷取出合適的搜尋詞。", + "extraction_requirements_placeholder": "例如: 1. 目前時間為: {{cTime}}。\n你是實驗室預約助手,你的任務是幫助使用者預約實驗室,從文字中取得對應的預約資訊。\n\n2. 你是Google搜尋助手,需要從文字中提取出合適的搜尋字詞。", "feedback_text": "回饋文字", "field_description": "欄位描述", "field_description_placeholder": "描述這個輸入欄位的功能,如果是工具呼叫參數,這個描述會影響模型產生的品質", diff --git a/projects/app/src/components/common/folder/SlideCard.tsx b/projects/app/src/components/common/folder/SlideCard.tsx index 90773bed31ab..0f39051c0c69 100644 --- a/projects/app/src/components/common/folder/SlideCard.tsx +++ b/projects/app/src/components/common/folder/SlideCard.tsx @@ -7,7 +7,6 @@ import MyDivider from '@fastgpt/web/components/common/MyDivider'; import { useTranslation } from 'next-i18next'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { PermissionValueType } from '@fastgpt/global/support/permission/type'; -import DefaultPermissionList from '@/components/support/permission/DefaultPerList'; import CollaboratorContextProvider, { MemberManagerInputPropsType } from '../../support/permission/MemberManager/context'; @@ -24,7 +23,6 @@ const FolderSlideCard = ({ deleteTip, onDelete, - defaultPer, managePer, isInheritPermission, resumeInheritPermission, @@ -39,11 +37,6 @@ const FolderSlideCard = ({ deleteTip: string; onDelete: () => void; - defaultPer?: { - value: PermissionValueType; - defaultValue: PermissionValueType; - onChange: (v: PermissionValueType) => Promise; - }; managePer: MemberManagerInputPropsType; isInheritPermission?: boolean; diff --git a/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx b/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx deleted file mode 100644 index 8798da50b7b9..000000000000 --- a/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { useContextSelector } from 'use-context-selector'; -import { CollaboratorContext } from './context'; -import { AddModalPropsType } from './MemberModal'; -import MemberModal from './MemberModal'; - -function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) { - const context = useContextSelector(CollaboratorContext, (v) => v); - return ; -} - -export default AddMemberModal; diff --git a/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx index e7e08c5f26d5..dd854713fdac 100644 --- a/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx +++ b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx @@ -19,35 +19,31 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useTranslation } from 'next-i18next'; -import { useMemo, useState } from 'react'; -import { useContextSelector } from 'use-context-selector'; +import { useMemo, useRef, useState } from 'react'; import PermissionSelect from './PermissionSelect'; import PermissionTags from './PermissionTags'; -import { MemberManagerPropsType } from './context'; import { DEFAULT_ORG_AVATAR, DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants'; import Path from '@/components/common/folder/Path'; import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant'; import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; import { OrgType } from '@fastgpt/global/support/user/team/org/type'; -import { createMemberPermission } from '@/web/support/user/team/api'; +import { useContextSelector } from 'use-context-selector'; +import { CollaboratorContext } from './context'; -export type AddModalPropsType = { - onClose: () => void; - mode?: 'member' | 'all'; +const HoverBoxStyle = { + bgColor: 'myGray.50', + cursor: 'pointer' }; -function MemberModal({ - onClose, - mode = 'member', - collaboratorContext: context -}: AddModalPropsType & { collaboratorContext?: MemberManagerPropsType }) { +function MemberModal({ onClose }: { onClose: () => void }) { const { t } = useTranslation(); const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, myGroups, loadAndGetOrgs } = useUserStore(); + const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList); + const [searchText, setSearchText] = useState(''); const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>(); - const [parentPath, setParentPath] = useState(''); const { data: [members = [], groups = [], orgs = []] = [], loading: loadingMembersAndGroups } = useRequest2( @@ -65,13 +61,7 @@ function MemberModal({ } ); - const currentOrg = useMemo(() => { - const splitPath = parentPath.split('/'); - const currentOrgId = splitPath[splitPath.length - 1]; - if (!currentOrgId) return; - - return orgs.find((org) => org.pathId === currentOrgId); - }, [orgs, parentPath]); + const [parentPath, setParentPath] = useState(''); const paths = useMemo(() => { const splitPath = parentPath.split('/').filter(Boolean); return splitPath @@ -88,30 +78,17 @@ function MemberModal({ .filter(Boolean) as ParentTreePathItemType[]; }, [parentPath, orgs]); - const filterMembers = useMemo(() => { - if (!searchText && filterClass !== 'member' && filterClass !== 'org') return []; - if (searchText) return members.filter((item) => item.memberName.includes(searchText)); - if (filterClass === 'org') { - if (!currentOrg) return []; - return members.filter((item) => currentOrg.members.find((v) => v.tmbId === item.tmbId)); - } - return members; - }, [members, searchText, filterClass, currentOrg]); - - const filterGroups = useMemo(() => { - if (mode !== 'all') return []; - if (!searchText && filterClass !== 'group') return []; - if (searchText) return groups.filter((item) => item.name.includes(searchText)); - return groups.filter((item) => { - if (context === undefined || context.permission.isOwner) return true; // owner can see all groups - return !myGroups.find((i) => String(i._id) === String(item._id)); - }); - }, [groups, searchText, filterClass, myGroups, mode, context]); + const [selectedOrgIdList, setSelectedOrgIdList] = useState([]); + const currentOrg = useMemo(() => { + const splitPath = parentPath.split('/'); + const currentOrgId = splitPath[splitPath.length - 1]; + if (!currentOrgId) return; + return orgs.find((org) => org.pathId === currentOrgId); + }, [orgs, parentPath]); const filterOrgs: (OrgType & { count?: number })[] = useMemo(() => { - if (mode !== 'all') return []; - if (!searchText && filterClass !== 'org') return []; if (searchText) return orgs.filter((item) => item.name.includes(searchText)); + if (!searchText && filterClass !== 'org') return []; if (parentPath === '') { setParentPath(`/${orgs[0].pathId}`); return []; @@ -123,44 +100,97 @@ function MemberModal({ count: item.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(item)).length })); - }, [orgs, searchText, filterClass, mode, parentPath]); + }, [orgs, searchText, filterClass, parentPath]); const [selectedMemberIdList, setSelectedMembers] = useState([]); + const filterMembers = useMemo(() => { + if (searchText) return members.filter((item) => item.memberName.includes(searchText)); + if (!searchText && filterClass !== 'member' && filterClass !== 'org') return []; + if (filterClass === 'org') { + if (!currentOrg) return []; + return members.filter((item) => currentOrg.members.find((v) => v.tmbId === item.tmbId)); + } + return members; + }, [members, searchText, filterClass, currentOrg]); + const [selectedGroupIdList, setSelectedGroupIdList] = useState([]); - const [selectedOrgIdList, setSelectedOrgIdList] = useState([]); - const [selectedPermission, setSelectedPermission] = useState( - context?.permissionList['read'].value - ); + const filterGroups = useMemo(() => { + if (searchText) return groups.filter((item) => item.name.includes(searchText)); + if (!searchText && filterClass !== 'group') return []; + return groups.filter((item) => { + return !myGroups.find((i) => String(i._id) === String(item._id)); + }); + }, [groups, searchText, filterClass, myGroups]); + + const permissionList = useContextSelector(CollaboratorContext, (v) => v.permissionList); + const getPerLabelList = useContextSelector(CollaboratorContext, (v) => v.getPerLabelList); + const [selectedPermission, setSelectedPermission] = useState(); const perLabel = useMemo(() => { - if (context) return context.getPerLabelList(selectedPermission!).join('、'); - }, [context, selectedPermission]); + if (selectedPermission === undefined) return ''; + return getPerLabelList(selectedPermission!).join('、'); + }, [getPerLabelList, selectedPermission]); + const onUpdateCollaborators = useContextSelector( + CollaboratorContext, + (v) => v.onUpdateCollaborators + ); const { runAsync: onConfirm, loading: isUpdating } = useRequest2( - () => { - if (context) { - return context.onUpdateCollaborators({ - members: selectedMemberIdList, - groups: selectedGroupIdList, - orgs: selectedOrgIdList, - permission: selectedPermission! - }); - } else { - return createMemberPermission({ - tmbId: selectedMemberIdList, - groupId: selectedGroupIdList, - orgId: selectedOrgIdList - }); - } - }, + () => + onUpdateCollaborators({ + members: selectedMemberIdList, + groups: selectedGroupIdList, + orgs: selectedOrgIdList, + permission: selectedPermission! + }), { successToast: t('common:common.Add Success'), - errorToast: 'Error', onSuccess() { onClose(); } } ); + const entryList = useRef([ + { label: t('user:team.group.members'), icon: '/imgs/avatar/BlueAvatar.svg', value: 'member' }, + { label: t('user:team.org.org'), icon: DEFAULT_ORG_AVATAR, value: 'org' }, + { label: t('user:team.group.group'), icon: DEFAULT_TEAM_AVATAR, value: 'group' } + ]); + + const selectedList = useMemo(() => { + const selectedOrgs = orgs.filter((org) => selectedOrgIdList.includes(org._id)); + const selectedGroups = groups.filter((group) => selectedGroupIdList.includes(group._id)); + const selectedMembers = members.filter((member) => selectedMemberIdList.includes(member.tmbId)); + + return [ + ...selectedOrgs.map((item) => ({ + id: `org-${item._id}`, + avatar: item.avatar, + name: item.name, + onDelete: () => setSelectedOrgIdList(selectedOrgIdList.filter((v) => v !== item._id)) + })), + ...selectedGroups.map((item) => ({ + id: `group-${item._id}`, + avatar: item.avatar, + name: item.name === DefaultGroupName ? userInfo?.team.teamName : item.name, + onDelete: () => setSelectedGroupIdList(selectedGroupIdList.filter((v) => v !== item._id)) + })), + ...selectedMembers.map((item) => ({ + id: `member-${item.tmbId}`, + avatar: item.avatar, + name: item.memberName, + onDelete: () => setSelectedMembers(selectedMemberIdList.filter((v) => v !== item.tmbId)) + })) + ]; + }, [ + orgs, + groups, + members, + selectedOrgIdList, + selectedGroupIdList, + selectedMemberIdList, + userInfo?.team.teamName + ]); + return ( @@ -181,73 +211,49 @@ function MemberModal({ gridTemplateColumns="1fr 1fr" h={'100%'} > - + setSearchText(e.target.value)} /> - - {!searchText && - (filterClass === undefined ? ( - <> - setFilterClass('member')} - > - - - {t('user:team.group.members')} - - - - setFilterClass('org')} - > - - - {t('user:team.org.org')} - - - - setFilterClass('group')} - > - - - {t('user:team.group.group')} - - - - - ) : ( + + {!searchText && !filterClass && ( + <> + {entryList.current.map((item) => { + return ( + setFilterClass(item.value as any)} + > + + + {item.label} + + + + ); + })} + + )} + + {/* Path */} + {!searchText && filterClass && ( + - ))} - {filterOrgs.map((org) => { - const onChange = () => { - setSelectedOrgIdList((state) => { - if (state.includes(org._id)) { - return state.filter((v) => v !== org._id); - } - return [...state, org._id]; - }); - }; - const collaborator = context?.collaboratorList?.find((v) => v.orgId === org._id); - return ( - - - - - {org.name} + + )} + + + {filterMembers.map((member) => { + const onChange = () => { + setSelectedMembers((state) => { + if (state.includes(member.tmbId)) { + return state.filter((v) => v !== member.tmbId); + } + return [...state, member.tmbId]; + }); + }; + const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId); + return ( + + + + + {member.memberName} + + + + ); + })} + {filterOrgs.map((org) => { + const onChange = () => { + setSelectedOrgIdList((state) => { + if (state.includes(org._id)) { + return state.filter((v) => v !== org._id); + } + return [...state, org._id]; + }); + }; + const collaborator = collaboratorList?.find((v) => v.orgId === org._id); + return ( + + + + + {org.name} + {org.count && ( + <> + + {org.count} + + + )} + + {org.count && ( - <> - - {org.count} - - + { + setParentPath(getOrgChildrenPath(org)); + }} + /> )} - {!!collaborator && ( - - )} - {org.count && ( - { - setParentPath(getOrgChildrenPath(org)); - }} + ); + })} + {filterGroups.map((group) => { + const onChange = () => { + setSelectedGroupIdList((state) => { + if (state.includes(group._id)) { + return state.filter((v) => v !== group._id); + } + return [...state, group._id]; + }); + }; + const collaborator = collaboratorList?.find((v) => v.groupId === group._id); + return ( + + - )} - - ); - })} - {filterGroups.map((group) => { - const onChange = () => { - setSelectedGroupIdList((state) => { - if (state.includes(group._id)) { - return state.filter((v) => v !== group._id); - } - return [...state, group._id]; - }); - }; - const collaborator = context?.collaboratorList?.find( - (v) => v.groupId === group._id - ); - return ( - - - - - {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name} - - {!!collaborator && ( - - )} - - ); - })} - {filterMembers.map((member) => { - const onChange = () => { - setSelectedMembers((state) => { - if (state.includes(member.tmbId)) { - return state.filter((v) => v !== member.tmbId); - } - return [...state, member.tmbId]; - }); - }; - const collaborator = context?.collaboratorList?.find( - (v) => v.tmbId === member.tmbId - ); - return ( - - - - - {member.memberName} - - {!!collaborator && ( - - )} - - ); - })} + + + {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name} + + + + ); + })} + - + + {`${t('user:has_chosen')}: `} {selectedMemberIdList.length + selectedGroupIdList.length + selectedOrgIdList.length} - - {selectedOrgIdList.map((orgId) => { - const org = orgs.find((v) => String(v._id) === orgId); + + {selectedList.map((item) => { return ( - setSelectedOrgIdList(selectedOrgIdList.filter((v) => v !== orgId)) - } + _hover={HoverBoxStyle} > - + - {org?.name} + {item.name} ); })} - {selectedGroupIdList.map((groupId) => { - const onChange = () => { - setSelectedGroupIdList((state) => { - if (state.includes(groupId)) { - return state.filter((v) => v !== groupId); - } - return [...state, groupId]; - }); - }; - const group = groups.find((v) => String(v._id) === groupId); - return ( - - - - {group?.name === DefaultGroupName ? userInfo?.team.teamName : group?.name} - - - - ); - })} - {selectedMemberIdList.map((tmbId) => { - const member = members.find((v) => v.tmbId === tmbId); - return member ? ( - - setSelectedMembers(selectedMemberIdList.filter((v) => v !== tmbId)) - } - > - - - {member.memberName} - - - - ) : null; - })} - {selectedPermission && ( + {!!permissionList && ( v); const ref = useRef(null); const closeTimer = useRef(); + const { permission, permissionList } = useContextSelector(CollaboratorContext, (v) => v); + const [isOpen, setIsOpen] = useState(false); const permissionSelectList = useMemo(() => { + if (!permissionList) return { singleCheckBoxList: [], multipleCheckBoxList: [] }; + const list = Object.entries(permissionList).map(([_, value]) => { return { name: value.name, @@ -77,6 +80,8 @@ function PermissionSelect({ }; }, [permission.isOwner, permissionList]); const selectedSingleValue = useMemo(() => { + if (!permissionList) return undefined; + const per = new Permission({ per: value }); if (per.hasManagePer) return permissionList['manage'].value; @@ -107,7 +112,7 @@ function PermissionSelect({ } }); - return ( + return selectedSingleValue !== undefined ? ( - ); + ) : null; } export default React.memo(PermissionSelect); diff --git a/projects/app/src/components/support/permission/MemberManager/PermissionTags.tsx b/projects/app/src/components/support/permission/MemberManager/PermissionTags.tsx index 7d9f7de16def..5bda6929c84e 100644 --- a/projects/app/src/components/support/permission/MemberManager/PermissionTags.tsx +++ b/projects/app/src/components/support/permission/MemberManager/PermissionTags.tsx @@ -7,12 +7,15 @@ import { CollaboratorContext } from './context'; import { useTranslation } from 'next-i18next'; export type PermissionTagsProp = { - permission: PermissionValueType; + permission?: PermissionValueType; }; function PermissionTags({ permission }: PermissionTagsProp) { const { getPerLabelList } = useContextSelector(CollaboratorContext, (v) => v); const { t } = useTranslation(); + + if (permission === undefined) return null; + const perTagList = getPerLabelList(permission); return ( diff --git a/projects/app/src/components/support/permission/MemberManager/context.tsx b/projects/app/src/components/support/permission/MemberManager/context.tsx index dd13afafc195..b46c6d7863df 100644 --- a/projects/app/src/components/support/permission/MemberManager/context.tsx +++ b/projects/app/src/components/support/permission/MemberManager/context.tsx @@ -19,19 +19,19 @@ import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useI18n } from '@/web/context/I18n'; import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; -const AddMemberModal = dynamic(() => import('./AddMemberModal')); + +const MemberModal = dynamic(() => import('./MemberModal')); const ManageModal = dynamic(() => import('./ManageModal')); export type MemberManagerInputPropsType = { permission: Permission; onGetCollaboratorList: () => Promise; - permissionList: PermissionListType; + permissionList?: PermissionListType; onUpdateCollaborators: (props: UpdateClbPermissionProps) => Promise; onDelOneCollaborator: ( props: RequireOnlyOne<{ tmbId: string; groupId: string; orgId: string }> ) => Promise; refreshDeps?: any[]; - mode?: 'member' | 'all'; }; export type MemberManagerPropsType = MemberManagerInputPropsType & { @@ -80,8 +80,7 @@ const CollaboratorContextProvider = ({ refetchResource, refreshDeps = [], isInheritPermission, - hasParent, - mode = 'member' + hasParent }: MemberManagerInputPropsType & { children: (props: ChildrenProps) => ReactNode; refetchResource?: () => void; @@ -121,6 +120,8 @@ const CollaboratorContextProvider = ({ const getPerLabelList = useCallback( (per: PermissionValueType) => { + if (!permissionList) return []; + const Per = new Permission({ per }); const labels: string[] = []; @@ -128,7 +129,7 @@ const CollaboratorContextProvider = ({ labels.push(permissionList['manage'].name); } else if (Per.hasWritePer) { labels.push(permissionList['write'].name); - } else { + } else if (Per.hasReadPer) { labels.push(permissionList['read'].name); } @@ -203,12 +204,11 @@ const CollaboratorContextProvider = ({ MemberListCard })} {isOpenAddMember && ( - { onCloseAddMember(); refetchResource?.(); }} - mode={mode} /> )} {isOpenManageModal && ( diff --git a/projects/app/src/pages/account/model/components/DefaultModal.tsx b/projects/app/src/pages/account/model/components/DefaultModal.tsx new file mode 100644 index 000000000000..2568c9500468 --- /dev/null +++ b/projects/app/src/pages/account/model/components/DefaultModal.tsx @@ -0,0 +1,84 @@ +import React, { useMemo, useState } from 'react'; +import MyModal from '@fastgpt/web/components/common/MyModal'; +import { useTranslation } from 'next-i18next'; +import { Box, Flex, ModalBody } from '@chakra-ui/react'; +import { MultipleRowArraySelect } from '@fastgpt/web/components/common/MySelect/MultipleRowSelect'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import { ModelProviderList } from '@fastgpt/global/core/ai/provider'; +import Avatar from '@fastgpt/web/components/common/Avatar'; +import { HUGGING_FACE_ICON } from '@fastgpt/global/common/system/constants'; +import { getModelFromList } from '@fastgpt/global/core/ai/model'; + +const DefaultModal = ({ onClose }: { onClose: () => void }) => { + const { t } = useTranslation(); + const { llmModelList, vectorModelList, whisperModel, audioSpeechModelList, reRankModelList } = + useSystemStore(); + const [value, setValue] = useState([]); + + const modelList = useMemo(() => { + return [ + ...llmModelList, + ...vectorModelList, + ...audioSpeechModelList, + ...reRankModelList, + whisperModel + ].map((item) => ({ + provider: item.provider, + name: item.name, + model: item.model + })); + }, [llmModelList, vectorModelList, whisperModel, audioSpeechModelList, reRankModelList]); + + const selectorList = useMemo(() => { + const renderList = ModelProviderList.map<{ + label: React.JSX.Element; + value: string; + children: { label: string | React.ReactNode; value: string }[]; + }>((provider) => ({ + label: ( + + + {t(provider.name as any)} + + ), + value: provider.id, + children: [] + })); + + for (const item of modelList) { + const modelData = getModelFromList(modelList, item.model); + const provider = + renderList.find((item) => item.value === (modelData?.provider || 'Other')) ?? + renderList[renderList.length - 1]; + + provider.children.push({ + label: modelData.name, + value: modelData.model + }); + } + + return renderList.filter((item) => item.children.length > 0); + }, [modelList, t]); + + console.log(selectorList); + + return ( + + 11 + + ); +}; + +export default DefaultModal; diff --git a/projects/app/src/pages/account/model/index.tsx b/projects/app/src/pages/account/model/index.tsx index 6af07f08773e..c0c1b5f50d48 100644 --- a/projects/app/src/pages/account/model/index.tsx +++ b/projects/app/src/pages/account/model/index.tsx @@ -1,15 +1,72 @@ import { serviceSideProps } from '@fastgpt/web/common/system/nextjs'; -import React from 'react'; +import React, { useState } from 'react'; import AccountContainer from '../components/AccountContainer'; -import { Box } from '@chakra-ui/react'; +import { Box, Button, Flex, useDisclosure } from '@chakra-ui/react'; import ModelTable from '@/components/core/ai/ModelTable'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs'; +import { useTranslation } from 'next-i18next'; +import MyMenu from '@fastgpt/web/components/common/MyMenu'; +import dynamic from 'next/dynamic'; + +const DefaultModal = dynamic(() => import('./components/DefaultModal'), { + ssr: false +}); const ModelProvider = () => { + const { t } = useTranslation(); + const { userInfo } = useUserStore(); + const isRoot = userInfo?.username === 'root'; + + const [tab, setTab] = useState<'model' | 'channel'>('model'); + + const { isOpen: isOpenDefault, onOpen: onOpenDefault, onClose: onCloseDefault } = useDisclosure(); + return ( - - - + + {/* Header */} + {/* + + list={[ + { label: t('account:active_model'), value: 'model' }, + { label: t('account:channel'), value: 'channel' } + ]} + value={tab} + px={8} + py={1} + onChange={setTab} + /> + + {tab === 'model' && ( + {t('account:create_model')}} + menuList={[ + { + children: [ + { + label: t('account:default_model'), + onClick: onOpenDefault + }, + { + label: t('account:custom_model') + } + ] + } + ]} + /> + )} + {tab === 'channel' && } + */} + + {tab === 'model' && } + {/* {tab === 'channel' && } */} + + + + {isOpenDefault && } ); }; diff --git a/projects/app/src/pages/account/team/components/GroupManage/index.tsx b/projects/app/src/pages/account/team/components/GroupManage/index.tsx index dd7b95a4696a..ba25e90b673f 100644 --- a/projects/app/src/pages/account/team/components/GroupManage/index.tsx +++ b/projects/app/src/pages/account/team/components/GroupManage/index.tsx @@ -1,6 +1,8 @@ import AvatarGroup from '@fastgpt/web/components/common/Avatar/AvatarGroup'; import { Box, + Button, + Flex, HStack, Table, TableContainer, @@ -29,17 +31,32 @@ import { useState } from 'react'; import IconButton from '../OrgManage/IconButton'; const ChangeOwnerModal = dynamic(() => import('./GroupTransferOwnerModal')); +const GroupInfoModal = dynamic(() => import('./GroupInfoModal')); +const ManageGroupMemberModal = dynamic(() => import('./GroupManageMember')); -function MemberTable({ - onEditGroup, - onManageMember -}: { - onEditGroup: (groupId: string) => void; - onManageMember: (groupId: string) => void; -}) { +function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { const { t } = useTranslation(); const { userInfo } = useUserStore(); + const [editGroupId, setEditGroupId] = useState(); + const { + isOpen: isOpenGroupInfo, + onOpen: onOpenGroupInfo, + onClose: onCloseGroupInfo + } = useDisclosure(); + const { + isOpen: isOpenManageGroupMember, + onOpen: onOpenManageGroupMember, + onClose: onCloseManageGroupMember + } = useDisclosure(); + const onEditGroup = (groupId: string) => { + setEditGroupId(groupId); + onOpenGroupInfo(); + }; + const onManageMember = (groupId: string) => { + setEditGroupId(groupId); + onOpenManageGroupMember(); + }; const { ConfirmModal: ConfirmDeleteGroupModal, openConfirm: openDeleteGroupModal } = useConfirm({ type: 'delete', @@ -73,145 +90,184 @@ function MemberTable({ onOpen: onOpenChangeOwner, onClose: onCloseChangeOwner } = useDisclosure(); - const onChangeOwner = (groupId: string) => { setEditGroupId(groupId); onOpenChangeOwner(); }; return ( - - - - - - - - - - - - - {groups?.map((group) => ( - - + + ))} + +
- {t('account_team:group_name')} - {t('account_team:owner')}{t('account_team:member')} - {t('common:common.Action')} -
- + <> + + {Tabs} + {userInfo?.team.permission.hasManagePer && ( + + )} + + + + + + + + + + + + + + + {groups?.map((group) => ( + + + - - - + + - - ))} - -
+ {t('account_team:group_name')} + {t('account_team:owner')}{t('account_team:member')} + {t('common:common.Action')} +
+ + + + ({group.name === DefaultGroupName ? members.length : group.members.length}) + + + item.role === 'owner')?.memberName ?? '' + : members.find( + (item) => + item.tmbId === + group.members.find((item) => item.role === 'owner')?.tmbId + )?.memberName ?? '' + } + avatar={ + group.name === DefaultGroupName + ? members.find((item) => item.role === 'owner')?.avatar ?? '' + : members.find( + (i) => + i.tmbId === + group.members.find((item) => item.role === 'owner')?.tmbId + )?.avatar ?? '' } - avatar={group.avatar} - /> - - ({group.name === DefaultGroupName ? members.length : group.members.length}) - - - - item.role === 'owner')?.memberName ?? '' - : members.find( - (item) => - item.tmbId === - group.members.find((item) => item.role === 'owner')?.tmbId - )?.memberName ?? '' - } - avatar={ - group.name === DefaultGroupName - ? members.find((item) => item.role === 'owner')?.avatar ?? '' - : members.find( - (i) => - i.tmbId === group.members.find((item) => item.role === 'owner')?.tmbId - )?.avatar ?? '' - } - /> - - {group.name === DefaultGroupName ? ( - v.avatar)} groupId={group._id} /> - ) : hasGroupManagePer(group) ? ( - - onManageMember(group._id)}> - members.find((m) => m.tmbId === v.tmbId)?.avatar ?? '' - )} - groupId={group._id} - /> - - - ) : ( - members.find((m) => m.tmbId === v.tmbId)?.avatar ?? '' - )} - groupId={group._id} /> - )} - - {hasGroupManagePer(group) && group.name !== DefaultGroupName && ( - } - menuList={[ - { - children: [ - { - label: t('account_team:edit_info'), - icon: 'edit', - onClick: () => { - onEditGroup(group._id); - } - }, - { - label: t('account_team:manage_member'), - icon: 'support/team/group', - onClick: () => { - onManageMember(group._id); - } - }, - ...(isGroupOwner(group) - ? [ - { - label: t('account_team:transfer_ownership'), - icon: 'modal/changePer', - onClick: () => { - onChangeOwner(group._id); - }, - type: 'primary' as MenuItemType - }, - { - label: t('common:common.Delete'), - icon: 'delete', - onClick: () => { - openDeleteGroupModal(() => delDeleteGroup(group._id))(); + + {group.name === DefaultGroupName ? ( + v.avatar)} groupId={group._id} /> + ) : hasGroupManagePer(group) ? ( + + onManageMember(group._id)}> + members.find((m) => m.tmbId === v.tmbId)?.avatar ?? '' + )} + groupId={group._id} + /> + + + ) : ( + members.find((m) => m.tmbId === v.tmbId)?.avatar ?? '' + )} + groupId={group._id} + /> + )} + + {hasGroupManagePer(group) && group.name !== DefaultGroupName && ( + } + menuList={[ + { + children: [ + { + label: t('account_team:edit_info'), + icon: 'edit', + onClick: () => { + onEditGroup(group._id); + } + }, + { + label: t('account_team:manage_member'), + icon: 'support/team/group', + onClick: () => { + onManageMember(group._id); + } + }, + ...(isGroupOwner(group) + ? [ + { + label: t('account_team:transfer_ownership'), + icon: 'modal/changePer', + onClick: () => { + onChangeOwner(group._id); + }, + type: 'primary' as MenuItemType }, - type: 'danger' as MenuItemType - } - ] - : []) - ] - } - ]} - /> - )} -
-
+ { + label: t('common:common.Delete'), + icon: 'delete', + onClick: () => { + openDeleteGroupModal(() => delDeleteGroup(group._id))(); + }, + type: 'danger' as MenuItemType + } + ] + : []) + ] + } + ]} + /> + )} +
+
+
+ {isOpenChangeOwner && editGroupId && ( )} - + {isOpenGroupInfo && ( + { + onCloseGroupInfo(); + setEditGroupId(undefined); + }} + editGroupId={editGroupId} + /> + )} + {isOpenManageGroupMember && ( + { + onCloseManageGroupMember(); + setEditGroupId(undefined); + }} + editGroupId={editGroupId} + /> + )} + ); } diff --git a/projects/app/src/pages/account/team/components/MemberTable.tsx b/projects/app/src/pages/account/team/components/MemberTable.tsx index b42dc837dcfc..08b9af2bcfea 100644 --- a/projects/app/src/pages/account/team/components/MemberTable.tsx +++ b/projects/app/src/pages/account/team/components/MemberTable.tsx @@ -1,106 +1,224 @@ import Avatar from '@fastgpt/web/components/common/Avatar'; -import { Box, HStack, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'; +import { + Box, + Button, + Flex, + HStack, + Table, + TableContainer, + Tbody, + Td, + Th, + Thead, + Tr, + useDisclosure +} from '@chakra-ui/react'; import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; import { useTranslation } from 'next-i18next'; import { useUserStore } from '@/web/support/user/useUserStore'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; -import MyBox from '@fastgpt/web/components/common/MyBox'; import { delRemoveMember } from '@/web/support/user/team/api'; import Tag from '@fastgpt/web/components/common/Tag'; import Icon from '@fastgpt/web/components/common/Icon'; import GroupTags from '@/components/support/permission/Group/GroupTags'; import { useContextSelector } from 'use-context-selector'; import { TeamContext } from './context'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import dynamic from 'next/dynamic'; +import { useToast } from '@fastgpt/web/hooks/useToast'; +import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { delLeaveTeam } from '@/web/support/user/team/api'; -function MemberTable() { - const { userInfo } = useUserStore(); +const InviteModal = dynamic(() => import('./InviteModal')); +const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal')); + +function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { const { t } = useTranslation(); + const { toast } = useToast(); + const { userInfo, teamPlanStatus } = useUserStore(); + const { feConfigs, setNotSufficientModalType } = useSystemStore(); + + const { groups, refetchGroups, myTeams, refetchTeams, members, refetchMembers, onSwitchTeam } = + useContextSelector(TeamContext, (v) => v); + + const { + isOpen: isOpenTeamTagsAsync, + onOpen: onOpenTeamTagsAsync, + onClose: onCloseTeamTagsAsync + } = useDisclosure(); + const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure(); const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm({ type: 'delete' }); - const { members, groups, refetchMembers, refetchGroups } = useContextSelector( - TeamContext, - (v) => v + const { runAsync: onLeaveTeam } = useRequest2( + async () => { + const defaultTeam = myTeams.find((item) => item.defaultTeam) || myTeams[0]; + // change to personal team + onSwitchTeam(defaultTeam.teamId); + return delLeaveTeam(); + }, + { + onSuccess() { + refetchTeams(); + }, + errorToast: t('account_team:user_team_leave_team_failed') + } ); + const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({ + content: t('account_team:confirm_leave_team') + }); return ( - - - - - - - - - - - - {members?.map((item) => ( - - - - + <> + + {Tabs} + + {userInfo?.team.permission.hasManagePer && feConfigs?.show_team_chat && ( + + )} + {userInfo?.team.permission.hasManagePer && ( + + )} + {!userInfo?.team.permission.isOwner && ( + + )} + + + + + +
- {t('account_team:user_name')} - {t('account_team:member_group')} - {t('common:common.Action')} -
- - - - {item.memberName} - {item.status === 'waiting' && ( - - {t('account_team:waiting')} - - )} - - - - group.members.map((m) => m.tmbId).includes(item.tmbId)) - .map((g) => g.name)} - max={3} - /> - - {userInfo?.team.permission.hasManagePer && - item.role !== TeamMemberRoleEnum.owner && - item.tmbId !== userInfo?.team.tmbId && ( - { - openRemoveMember( - () => - delRemoveMember(item.tmbId).then(() => - Promise.all([refetchGroups(), refetchMembers()]) - ), - undefined, - t('account_team:remove_tip', { - username: item.memberName - }) - )(); - }} - /> - )} -
+ + + + + - ))} - -
+ {t('account_team:user_name')} + {t('account_team:member_group')} + {t('common:common.Action')} +
+ + + {members?.map((item) => ( + + + + + + {item.memberName} + {item.status === 'waiting' && ( + + {t('account_team:waiting')} + + )} + + + + + group.members.map((m) => m.tmbId).includes(item.tmbId)) + .map((g) => g.name)} + max={3} + /> + + + {userInfo?.team.permission.hasManagePer && + item.role !== TeamMemberRoleEnum.owner && + item.tmbId !== userInfo?.team.tmbId && ( + { + openRemoveMember( + () => + delRemoveMember(item.tmbId).then(() => + Promise.all([refetchGroups(), refetchMembers()]) + ), + undefined, + t('account_team:remove_tip', { + username: item.memberName + }) + )(); + }} + /> + )} + + + ))} + + + + +
+ - - -
+ + {isOpenInvite && userInfo?.team?.teamId && ( + + )} + {isOpenTeamTagsAsync && } + ); } diff --git a/projects/app/src/pages/account/team/components/OrgManage/index.tsx b/projects/app/src/pages/account/team/components/OrgManage/index.tsx index b4fadcc701c1..241fc1aee2b7 100644 --- a/projects/app/src/pages/account/team/components/OrgManage/index.tsx +++ b/projects/app/src/pages/account/team/components/OrgManage/index.tsx @@ -71,7 +71,7 @@ function ActionButton({ ); } -function OrgTable() { +function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { const { t } = useTranslation(); const { userInfo, isTeamAdmin } = useUserStore(); @@ -157,99 +157,69 @@ function OrgTable() { }); return ( - - - - - - {/* Table */} - - - - - - - - - - {currentOrgs.map((org) => ( - - - + <> + + {Tabs} + + + + + + + {/* Table */} + +
- {t('common:Name')} - - {t('common:common.Action')} -
- setParentPath(getOrgChildrenPath(org))} - > - - {org.count} - - - - {isTeamAdmin && ( - } - menuList={[ - { - children: [ - { - icon: 'edit', - label: t('account_team:edit_info'), - onClick: () => setEditOrg(org) - }, - { - icon: 'common/file/move', - label: t('common:Move'), - onClick: () => setMovingOrg(org) - }, - { - icon: 'delete', - label: t('account_team:delete'), - type: 'danger', - onClick: () => deleteOrgHandler(org._id) - } - ] - } - ]} - /> - )} -
+ + + + - ))} - {currentOrg?.members.map((member) => { - const memberInfo = members.find((m) => m.tmbId === member.tmbId); - if (!memberInfo) return null; - - return ( - + + + {currentOrgs.map((org) => ( + - ); - })} - -
+ {t('common:Name')} + + {t('common:common.Action')} +
- + setParentPath(getOrgChildrenPath(org))} + > + + {org.count} + + {isTeamAdmin && ( } menuList={[ { children: [ + { + icon: 'edit', + label: t('account_team:edit_info'), + onClick: () => setEditOrg(org) + }, + { + icon: 'common/file/move', + label: t('common:Move'), + onClick: () => setMovingOrg(org) + }, { icon: 'delete', label: t('account_team:delete'), type: 'danger', - onClick: () => - openDeleteMemberModal(() => - deleteMemberReq(currentOrg._id, member.tmbId) - )() + onClick: () => deleteOrgHandler(org._id) } ] } @@ -258,91 +228,126 @@ function OrgTable() { )}
-
- {/* Slider */} - - - - - {currentOrg?.name} - - {currentOrg?.path !== '' && ( - setEditOrg(currentOrg)} /> - )} - - {currentOrg?.description || t('common:common.no_intro')} - - + ))} + {currentOrg?.members.map((member) => { + const memberInfo = members.find((m) => m.tmbId === member.tmbId); + if (!memberInfo) return null; - - {t('common:common.Action')} - - {currentOrg && isTeamAdmin && ( - - { - setEditOrg({ - ...defaultOrgForm, - parentId: currentOrg?._id - }); - }} - /> - setManageMemberOrg(currentOrg)} - /> + return ( + + + + + + {isTeamAdmin && ( + } + menuList={[ + { + children: [ + { + icon: 'delete', + label: t('account_team:delete'), + type: 'danger', + onClick: () => + openDeleteMemberModal(() => + deleteMemberReq(currentOrg._id, member.tmbId) + )() + } + ] + } + ]} + /> + )} + + + ); + })} + + + + {/* Slider */} + + + + + {currentOrg?.name} + {currentOrg?.path !== '' && ( - <> - setMovingOrg(currentOrg)} - /> - deleteOrgHandler(currentOrg._id)} - /> - + setEditOrg(currentOrg)} /> )} - - )} - -
+ + {currentOrg?.description || t('common:common.no_intro')} + + + + + {t('common:common.Action')} + + {currentOrg && isTeamAdmin && ( + + { + setEditOrg({ + ...defaultOrgForm, + parentId: currentOrg?._id + }); + }} + /> + setManageMemberOrg(currentOrg)} + /> + {currentOrg?.path !== '' && ( + <> + setMovingOrg(currentOrg)} + /> + deleteOrgHandler(currentOrg._id)} + /> + + )} + + )} + +
- {!!editOrg && ( - setEditOrg(undefined)} - onSuccess={refetchOrgs} - /> - )} - {!!movingOrg && ( - setMovingOrg(undefined)} - onSuccess={refetchOrgs} - /> - )} - {!!manageMemberOrg && ( - setManageMemberOrg(undefined)} - /> - )} + {!!editOrg && ( + setEditOrg(undefined)} + onSuccess={refetchOrgs} + /> + )} + {!!movingOrg && ( + setMovingOrg(undefined)} + onSuccess={refetchOrgs} + /> + )} + {!!manageMemberOrg && ( + setManageMemberOrg(undefined)} + /> + )} - - - + + + + ); } diff --git a/projects/app/src/pages/account/team/components/PermissionManage/index.tsx b/projects/app/src/pages/account/team/components/PermissionManage/index.tsx index 4c7b89bd7c70..cb97ba24cee5 100644 --- a/projects/app/src/pages/account/team/components/PermissionManage/index.tsx +++ b/projects/app/src/pages/account/team/components/PermissionManage/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo, useState } from 'react'; import { Box, Checkbox, @@ -10,7 +10,9 @@ import { Th, Thead, Text, - Tr + Tr, + Flex, + Button } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; @@ -26,442 +28,400 @@ import MemberTag from '../../../../../components/support/user/team/Info/MemberTa import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import { TeamManagePermissionVal, + TeamPermissionList, TeamWritePermissionVal } from '@fastgpt/global/support/permission/user/constant'; import { TeamPermission } from '@fastgpt/global/support/permission/user/controller'; -import { useCreation, useToggle } from 'ahooks'; +import { useToggle } from 'ahooks'; import MyIconButton from '@fastgpt/web/components/common/Icon/button'; -import MemberModal from '@/components/support/permission/MemberManager/MemberModal'; +import MyBox from '@fastgpt/web/components/common/MyBox'; +import CollaboratorContextProvider, { + CollaboratorContext +} from '@/components/support/permission/MemberManager/context'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; +import { useContextSelector } from 'use-context-selector'; +import { CollaboratorItemType } from '@fastgpt/global/support/permission/collaborator'; function PermissionManage({ - isOpenAddPermission, - onCloseAddPermission + Tabs, + onOpenAddMember }: { - isOpenAddPermission: boolean; - onCloseAddPermission: () => void; + Tabs: React.ReactNode; + onOpenAddMember: () => void; }) { const { t } = useTranslation(); const { userInfo } = useUserStore(); + const [searchKey, setSearchKey] = useState(''); - const { runAsync: refetchClbs, data: clbs = { tmb: [], group: [], org: [] } } = useRequest2( - getTeamClbs, - { - manual: false, - refreshDeps: [userInfo?.team?.teamId] - } + const collaboratorList = useContextSelector( + CollaboratorContext, + (state) => state.collaboratorList + ); + const onUpdateCollaborators = useContextSelector( + CollaboratorContext, + (state) => state.onUpdateCollaborators + ); + const onDelOneCollaborator = useContextSelector( + CollaboratorContext, + (state) => state.onDelOneCollaborator ); const [isExpandMember, setExpandMember] = useToggle(true); const [isExpandGroup, setExpandGroup] = useToggle(true); const [isExpandOrg, setExpandOrg] = useToggle(true); - const members = useCreation( - () => - clbs.tmb.map((item) => ({ - ...item, - permission: new TeamPermission({ per: item.permission }) - })), - [clbs] - ); + const { tmbList, groupList, orgList } = useMemo(() => { + const tmbList: CollaboratorItemType[] = []; + const groupList: CollaboratorItemType[] = []; + const orgList: CollaboratorItemType[] = []; - const groups = useCreation( - () => - clbs.group.map((item) => ({ - ...item, - permission: new TeamPermission({ per: item.permission }) - })), - [clbs] - ); + collaboratorList.forEach((item) => { + if (item.tmbId) { + tmbList.push(item); + } else if (item.groupId) { + groupList.push(item); + } else if (item.orgId) { + orgList.push(item); + } + }); - const orgs = useCreation( - () => - clbs.org.map((item) => ({ - ...item, - permission: new TeamPermission({ per: item.permission }) - })), - [clbs] - ); + return { + tmbList, + groupList, + orgList + }; + }, [collaboratorList]); - const { runAsync: onUpdateMemberPermission } = useRequest2(updateMemberPermission, { - onSuccess: () => { - refetchClbs(); - } - }); + const { runAsync: onUpdatePermission, loading: addLoading } = useRequest2( + async ({ id, type, per }: { id: string; type: 'add' | 'remove'; per: 'write' | 'manage' }) => { + const clb = collaboratorList.find( + (clb) => clb.tmbId === id || clb.groupId === id || clb.orgId === id + ); - const { runAsync: onAddPermission, loading: addLoading } = useRequest2( - async ({ - orgId, - groupId, - memberId, - per - }: { - orgId?: string; - groupId?: string; - memberId?: string; - per: 'write' | 'manage'; - }) => { - if (groupId) { - const group = groups?.find((group) => group.groupId === groupId); - if (group) { - const permission = new TeamPermission({ per: group.permission.value }); - switch (per) { - case 'write': - permission.addPer(TeamWritePermissionVal); - return onUpdateMemberPermission({ - groupId: group.groupId, - permission: permission.value - }); - case 'manage': - permission.addPer(TeamManagePermissionVal); - return onUpdateMemberPermission({ - groupId: group.groupId, - permission: permission.value - }); - } - } - } - if (orgId) { - const org = orgs.find((org) => String(org.orgId) === orgId); - if (org) { - const permission = new TeamPermission({ per: org.permission.value }); - switch (per) { - case 'write': - permission.addPer(TeamWritePermissionVal); - return onUpdateMemberPermission({ - orgId: org.orgId, - permission: permission.value - }); - case 'manage': - permission.addPer(TeamManagePermissionVal); - return onUpdateMemberPermission({ - orgId: org.orgId, - permission: permission.value - }); - } - } - } - if (memberId) { - const member = members?.find((member) => member.tmbId === memberId); - if (member) { - const permission = new TeamPermission({ per: member.permission.value }); - switch (per) { - case 'write': - permission.addPer(TeamWritePermissionVal); - return onUpdateMemberPermission({ - memberId: member.tmbId, - permission: permission.value - }); - case 'manage': - permission.addPer(TeamManagePermissionVal); - return onUpdateMemberPermission({ - memberId: member.tmbId, - permission: permission.value - }); - } - } - } - } - ); + if (!clb) return; - const { runAsync: onRemovePermission, loading: removeLoading } = useRequest2( - async ({ - orgId, - groupId, - memberId, - per - }: { - orgId?: string; - groupId?: string; - memberId?: string; - per: 'write' | 'manage'; - }) => { - if (groupId) { - const group = groups?.find((group) => group.groupId === groupId); - if (group) { - const permission = new TeamPermission({ per: group.permission.value }); - switch (per) { - case 'write': - permission.removePer(TeamWritePermissionVal); - return onUpdateMemberPermission({ - groupId: group.groupId, - permission: permission.value - }); - case 'manage': - permission.removePer(TeamManagePermissionVal); - return onUpdateMemberPermission({ - groupId: group.groupId, - permission: permission.value - }); - } - } - } - if (orgId) { - const org = orgs.find((org) => String(org.orgId) === orgId); - if (org) { - const permission = new TeamPermission({ per: org.permission.value }); - switch (per) { - case 'write': - permission.removePer(TeamWritePermissionVal); - return onUpdateMemberPermission({ - orgId: org.orgId, - permission: permission.value - }); - case 'manage': - permission.removePer(TeamManagePermissionVal); - return onUpdateMemberPermission({ - orgId: org.orgId, - permission: permission.value - }); - } - } - } - if (memberId) { - const member = members?.find((member) => String(member.tmbId) === memberId); - if (member) { - const permission = new TeamPermission({ per: member.permission.value }); // Hint: member.permission is read-only - switch (per) { - case 'write': - permission.removePer(TeamWritePermissionVal); - return onUpdateMemberPermission({ - memberId: String(member.tmbId), - permission: permission.value - }); - case 'manage': - permission.removePer(TeamManagePermissionVal); - return onUpdateMemberPermission({ - memberId: String(member.tmbId), - permission: permission.value - }); - } - } + const updatePer = per === 'write' ? TeamWritePermissionVal : TeamManagePermissionVal; + const permission = new TeamPermission({ per: clb.permission.value }); + if (type === 'add') { + permission.addPer(updatePer); + } else { + permission.removePer(updatePer); } + + return onUpdateCollaborators({ + ...(clb.tmbId && { members: [clb.tmbId] }), + ...(clb.groupId && { groups: [clb.groupId] }), + ...(clb.orgId && { orgs: [clb.orgId] }), + permission: permission.value + }); } ); - const { runAsync: onDeleteMemberPermission } = useRequest2(deleteMemberPermission, { - onSuccess: () => { - refetchClbs(); - } - }); + const { runAsync: onDeleteMemberPermission, loading: deleteLoading } = + useRequest2(onDelOneCollaborator); const userManage = userInfo?.permission.hasManagePer; return ( - - - - - - - - - - - - - - - {t('user:team.group.members')} - - - {isExpandMember && - members.map((member) => ( - - - + {isExpandGroup && + groupList.map((group) => ( + + + + + {userInfo?.permission.isOwner && ( + + )} + + ))} + + +
- {`${t('user:team.group.members')} / ${t('user:team.org.org')} / ${t('user:team.group.group')}`} - - - - {t('user:team.group.permission.write')} - - - - {t('user:team.group.permission.manage')} - - - - - {t('common:common.Action')} - -
- - - {member.name} - - + <> + + {Tabs} + + {/* setSearchKey(e.target.value)} + /> */} + + {userInfo?.team.permission.hasManagePer && ( + + )} + + + + + + + + - )} - - ))} - - - - - - {t('user:team.org.org')} - - - - {isExpandOrg && - orgs.map((org) => ( - - - - - )} + - ))} - - - - - - {t('user:team.group.group')} - - + + + <> + + + + {t('user:team.group.members')} + + + {isExpandMember && + tmbList.map((member) => ( + + + + + {userManage && + !member.permission.isOwner && + userInfo?.team.tmbId !== member.tmbId && ( + + )} + + ))} + - {isExpandGroup && - groups.map((group) => ( - - - + + + - - - + {isExpandOrg && + orgList.map((org) => ( + + + + + {userInfo?.permission.isOwner && ( + + )} + + ))} + + + <> + + + + - - - {userInfo?.permission.isOwner && ( - - )} - - ))} - -
+ {`${t('user:team.group.members')} / ${t('user:team.org.org')} / ${t('user:team.group.group')}`} + + - - e.target.checked - ? onAddPermission({ memberId: String(member.tmbId), per: 'write' }) - : onRemovePermission({ memberId: String(member.tmbId), per: 'write' }) - } - /> + {t('user:team.group.permission.write')} - - + + - - e.target.checked - ? onAddPermission({ memberId: String(member.tmbId), per: 'manage' }) - : onRemovePermission({ memberId: String(member.tmbId), per: 'manage' }) - } - /> + {t('user:team.group.permission.manage')} + - - {userManage && - !member.permission.isOwner && - userInfo?.team.tmbId !== member.tmbId && ( - - - onDeleteMemberPermission({ tmbId: String(member.tmbId) })} - /> - -
- - - - - e.target.checked - ? onAddPermission({ orgId: org.orgId, per: 'write' }) - : onRemovePermission({ orgId: org.orgId, per: 'write' }) - } - /> - - + + - - e.target.checked - ? onAddPermission({ orgId: org.orgId, per: 'manage' }) - : onRemovePermission({ orgId: org.orgId, per: 'manage' }) - } - /> + {t('common:common.Action')} - - {userInfo?.permission.isOwner && ( - - - onDeleteMemberPermission({ orgId: org.orgId })} - /> - -
+ + + {member.name} + + + + + e.target.checked + ? onUpdatePermission({ + id: member.tmbId!, + type: 'add', + per: 'write' + }) + : onUpdatePermission({ + id: member.tmbId!, + type: 'remove', + per: 'write' + }) + } + /> + + + + + e.target.checked + ? onUpdatePermission({ + id: member.tmbId!, + type: 'add', + per: 'manage' + }) + : onUpdatePermission({ + id: member.tmbId!, + type: 'remove', + per: 'manage' + }) + } + /> + + + + + onDeleteMemberPermission({ tmbId: String(member.tmbId) }) + } + /> + +
- - - - - e.target.checked - ? onAddPermission({ groupId: group.groupId, per: 'write' }) - : onRemovePermission({ groupId: group.groupId, per: 'write' }) - } + <> +
- - - e.target.checked - ? onAddPermission({ groupId: group.groupId, per: 'manage' }) - : onRemovePermission({ groupId: group.groupId, per: 'manage' }) - } + {t('user:team.org.org')} + +
+ + + + + e.target.checked + ? onUpdatePermission({ id: org.orgId!, type: 'add', per: 'write' }) + : onUpdatePermission({ + id: org.orgId!, + type: 'remove', + per: 'write' + }) + } + /> + + + + + e.target.checked + ? onUpdatePermission({ id: org.orgId!, type: 'add', per: 'manage' }) + : onUpdatePermission({ + id: org.orgId!, + type: 'remove', + per: 'manage' + }) + } + /> + + + + onDeleteMemberPermission({ orgId: org.orgId! })} + /> + +
- - onDeleteMemberPermission({ groupId: group.groupId })} - /> - -
- {isOpenAddPermission && ( - { - refetchClbs(); - onCloseAddPermission(); - }} - mode="all" - /> - )} -
+ {t('user:team.group.group')} + +
+ + + + + e.target.checked + ? onUpdatePermission({ + id: group.groupId!, + type: 'add', + per: 'write' + }) + : onUpdatePermission({ + id: group.groupId!, + type: 'remove', + per: 'write' + }) + } + /> + + + + + e.target.checked + ? onUpdatePermission({ + id: group.groupId!, + type: 'add', + per: 'manage' + }) + : onUpdatePermission({ + id: group.groupId!, + type: 'remove', + per: 'manage' + }) + } + /> + + + + onDeleteMemberPermission({ groupId: group.groupId! })} + /> + +
+
+ + ); } -export default PermissionManage; +export const Render = ({ Tabs }: { Tabs: React.ReactNode }) => { + const { userInfo } = useUserStore(); + + return userInfo?.team ? ( + + {({ onOpenAddMember }) => } + + ) : null; +}; + +export default Render; diff --git a/projects/app/src/pages/account/team/components/context.tsx b/projects/app/src/pages/account/team/components/context.tsx index 7ad30ad5a513..b76ed99c3afa 100644 --- a/projects/app/src/pages/account/team/components/context.tsx +++ b/projects/app/src/pages/account/team/components/context.tsx @@ -25,8 +25,6 @@ type TeamModalContextType = { refetchMembers: () => void; refetchTeams: () => void; refetchGroups: () => void; - searchKey: string; - setSearchKey: React.Dispatch>; teamSize: number; }; @@ -51,10 +49,6 @@ export const TeamContext = createContext({ throw new Error('Function not implemented.'); }, - searchKey: '', - setSearchKey: function (_value: React.SetStateAction): void { - throw new Error('Function not implemented.'); - }, teamSize: 0 }); @@ -62,7 +56,6 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode }) const { t } = useTranslation(); const [editTeamData, setEditTeamData] = useState(); const { userInfo, initUserInfo, loadAndGetTeamMembers } = useUserStore(); - const [searchKey, setSearchKey] = useState(''); const { data: myTeams = [], @@ -115,8 +108,6 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode }) refetchTeams, isLoading, onSwitchTeam, - searchKey, - setSearchKey, // create | update team setEditTeamData, diff --git a/projects/app/src/pages/account/team/index.tsx b/projects/app/src/pages/account/team/index.tsx index 772326870a2c..6307d5c4be02 100644 --- a/projects/app/src/pages/account/team/index.tsx +++ b/projects/app/src/pages/account/team/index.tsx @@ -1,33 +1,25 @@ import { serviceSideProps } from '@fastgpt/web/common/system/nextjs'; import AccountContainer from '../components/AccountContainer'; -import { Box, Button, Flex, useDisclosure } from '@chakra-ui/react'; +import { Box, Flex, useDisclosure } from '@chakra-ui/react'; import Icon from '@fastgpt/web/components/common/Icon'; import { useTranslation } from 'next-i18next'; import TeamSelector from '../components/TeamSelector'; import { useUserStore } from '@/web/support/user/useUserStore'; -import React, { useState } from 'react'; +import React, { useMemo } from 'react'; import { useContextSelector } from 'use-context-selector'; import { useRouter } from 'next/router'; import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs'; -import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useToast } from '@fastgpt/web/hooks/useToast'; -import { delLeaveTeam } from '@/web/support/user/team/api'; -import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; import { TeamContext, TeamModalContextProvider } from './components/context'; import dynamic from 'next/dynamic'; -import TeamTagModal from '@/components/support/user/team/TeamTagModal'; import MemberTable from './components/MemberTable'; -import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; -const InviteModal = dynamic(() => import('./components/InviteModal')); const PermissionManage = dynamic(() => import('./components/PermissionManage/index')); const GroupManage = dynamic(() => import('./components/GroupManage/index')); -const GroupInfoModal = dynamic(() => import('./components/GroupManage/GroupInfoModal')); -const ManageGroupMemberModal = dynamic(() => import('./components/GroupManage/GroupManageMember')); + const OrgManage = dynamic(() => import('./components/OrgManage/index')); export enum TeamTabEnum { @@ -39,79 +31,37 @@ export enum TeamTabEnum { const Team = () => { const router = useRouter(); - const { toast } = useToast(); - const { t } = useTranslation(); - const { userInfo, teamPlanStatus } = useUserStore(); - const { feConfigs, setNotSufficientModalType } = useSystemStore(); - - const { - myTeams, - refetchTeams, - members, - refetchMembers, - setEditTeamData, - onSwitchTeam, - searchKey, - setSearchKey, - teamSize, - isLoading - } = useContextSelector(TeamContext, (v) => v); - const { teamTab = TeamTabEnum.member } = router.query as { teamTab: `${TeamTabEnum}` }; - const { - isOpen: isOpenTeamTagsAsync, - onOpen: onOpenTeamTagsAsync, - onClose: onCloseTeamTagsAsync - } = useDisclosure(); - const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure(); - const { - isOpen: isOpenGroupInfo, - onOpen: onOpenGroupInfo, - onClose: onCloseGroupInfo - } = useDisclosure(); - const { - isOpen: isOpenManageGroupMember, - onOpen: onOpenManageGroupMember, - onClose: onCloseManageGroupMember - } = useDisclosure(); - const { - isOpen: isOpenAddPermission, - onOpen: onOpenAddPermission, - onClose: onCloseAddPermission - } = useDisclosure(); + const { t } = useTranslation(); + const { userInfo } = useUserStore(); - const { runAsync: onLeaveTeam } = useRequest2( - async () => { - const defaultTeam = myTeams.find((item) => item.defaultTeam) || myTeams[0]; - // change to personal team - // get members - onSwitchTeam(defaultTeam.teamId); - return delLeaveTeam(); - }, - { - onSuccess() { - refetchTeams(); - }, - errorToast: t('account_team:user_team_leave_team_failed') - } + const { setEditTeamData, teamSize, isLoading } = useContextSelector(TeamContext, (v) => v); + + const Tabs = useMemo( + () => ( + { + router.replace({ + query: { + ...router.query, + teamTab: e + } + }); + }} + /> + ), + [router, t, teamTab] ); - const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({ - content: t('account_team:confirm_leave_team') - }); - - const [editGroupId, setEditGroupId] = useState(); - const onEditGroup = (groupId: string) => { - setEditGroupId(groupId); - onOpenGroupInfo(); - }; - - const onManageMember = (groupId: string) => { - setEditGroupId(groupId); - onOpenManageGroupMember(); - }; - return ( {/* header */} @@ -175,159 +125,11 @@ const Team = () => { {/* table */} - - { - router.replace({ - query: { - ...router.query, - teamTab: e - } - }); - }} - /> - - - {teamTab === TeamTabEnum.member && - userInfo?.team.permission.hasManagePer && - feConfigs?.show_team_chat && ( - - )} - {teamTab === TeamTabEnum.member && userInfo?.team.permission.hasManagePer && ( - - )} - {teamTab === TeamTabEnum.member && !userInfo?.team.permission.isOwner && ( - - )} - {teamTab === TeamTabEnum.group && userInfo?.team.permission.hasManagePer && ( - - )} - {teamTab === TeamTabEnum.permission && ( - - setSearchKey(e.target.value)} - /> - - )} - {teamTab === TeamTabEnum.permission && userInfo?.team.permission.hasManagePer && ( - - )} - - - - {teamTab === TeamTabEnum.member && } - {teamTab === TeamTabEnum.group && ( - - )} - {teamTab === TeamTabEnum.org && } - {teamTab === TeamTabEnum.permission && ( - - )} - + {teamTab === TeamTabEnum.member && } + {teamTab === TeamTabEnum.org && } + {teamTab === TeamTabEnum.group && } + {teamTab === TeamTabEnum.permission && } - {isOpenInvite && userInfo?.team?.teamId && ( - - )} - {isOpenTeamTagsAsync && } - {isOpenGroupInfo && ( - { - onCloseGroupInfo(); - setEditGroupId(undefined); - }} - editGroupId={editGroupId} - /> - )} - {isOpenManageGroupMember && ( - { - onCloseManageGroupMember(); - setEditGroupId(undefined); - }} - editGroupId={editGroupId} - /> - )} - ); }; diff --git a/projects/app/src/pages/api/common/system/writefile.ts b/projects/app/src/pages/api/common/system/writefile.ts new file mode 100644 index 000000000000..feb458e5365e --- /dev/null +++ b/projects/app/src/pages/api/common/system/writefile.ts @@ -0,0 +1,25 @@ +import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; +import { NextAPI } from '@/service/middleware/entry'; +import * as fs from 'fs'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; + +export type writefileQuery = {}; + +export type writefileBody = { + name: string; + content: string; +}; + +export type writefileResponse = {}; + +async function handler( + req: ApiRequestProps, + res: ApiResponseType +): Promise { + await authCert({ req, authRoot: true }); + const { name, content } = req.body; + await fs.promises.writeFile(`public/${name}`, content); + return {}; +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/app/detail/components/InfoModal.tsx b/projects/app/src/pages/app/detail/components/InfoModal.tsx index b058bbe8b045..f82b1e931e21 100644 --- a/projects/app/src/pages/app/detail/components/InfoModal.tsx +++ b/projects/app/src/pages/app/detail/components/InfoModal.tsx @@ -187,7 +187,6 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => { )} getCollaboratorList(appDetail._id)} permissionList={AppPermissionList} diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx index d9165b2b0959..313f2c4567b6 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx @@ -57,7 +57,7 @@ const NodeCard = (props: Props) => { name = t('common:core.module.template.UnKnow Module'), intro, minW = '300px', - maxW = '600px', + maxW = '666px', minH = 0, w = 'full', h = 'full', diff --git a/projects/app/src/pages/app/list/components/List.tsx b/projects/app/src/pages/app/list/components/List.tsx index 72320796b4aa..85d1f070d5e0 100644 --- a/projects/app/src/pages/app/list/components/List.tsx +++ b/projects/app/src/pages/app/list/components/List.tsx @@ -431,7 +431,6 @@ const ListItem = () => { avatar={editPerApp.avatar} name={editPerApp.name} managePer={{ - mode: 'all', permission: editPerApp.permission, onGetCollaboratorList: () => getCollaboratorList(editPerApp._id), permissionList: AppPermissionList, diff --git a/projects/app/src/pages/app/list/index.tsx b/projects/app/src/pages/app/list/index.tsx index c4ebf94c515b..602652975f62 100644 --- a/projects/app/src/pages/app/list/index.tsx +++ b/projects/app/src/pages/app/list/index.tsx @@ -301,7 +301,6 @@ const MyApps = () => { deleteTip={t('app:confirm_delete_folder_tip')} onDelete={() => onDeleFolder(folderDetail._id)} managePer={{ - mode: 'all', permission: folderDetail.permission, onGetCollaboratorList: () => getCollaboratorList(folderDetail._id), permissionList: AppPermissionList, diff --git a/projects/app/src/pages/dataset/component/MemberManager.tsx b/projects/app/src/pages/dataset/component/MemberManager.tsx index c201d70eaf71..766403767e8f 100644 --- a/projects/app/src/pages/dataset/component/MemberManager.tsx +++ b/projects/app/src/pages/dataset/component/MemberManager.tsx @@ -1,4 +1,4 @@ -import { Box, Button, Flex, FormLabel } from '@chakra-ui/react'; +import { Box, Flex } from '@chakra-ui/react'; import React from 'react'; import CollaboratorContextProvider, { MemberManagerInputPropsType diff --git a/projects/app/src/pages/dataset/detail/components/Info/index.tsx b/projects/app/src/pages/dataset/detail/components/Info/index.tsx index d1412cc542ce..e3269664aa73 100644 --- a/projects/app/src/pages/dataset/detail/components/Info/index.tsx +++ b/projects/app/src/pages/dataset/detail/components/Info/index.tsx @@ -354,7 +354,6 @@ const Info = ({ datasetId }: { datasetId: string }) => { getCollaboratorList(datasetId), permissionList: DatasetPermissionList, diff --git a/projects/app/src/pages/dataset/list/component/List.tsx b/projects/app/src/pages/dataset/list/component/List.tsx index 4700ef19cae6..3300437d3dee 100644 --- a/projects/app/src/pages/dataset/list/component/List.tsx +++ b/projects/app/src/pages/dataset/list/component/List.tsx @@ -440,7 +440,6 @@ function List() { avatar={editPerDataset.avatar} name={editPerDataset.name} managePer={{ - mode: 'all', permission: editPerDataset.permission, onGetCollaboratorList: () => getCollaboratorList(editPerDataset._id), permissionList: DatasetPermissionList, diff --git a/projects/app/src/pages/dataset/list/index.tsx b/projects/app/src/pages/dataset/list/index.tsx index 0872e7bb4d0b..a1f96c350fbc 100644 --- a/projects/app/src/pages/dataset/list/index.tsx +++ b/projects/app/src/pages/dataset/list/index.tsx @@ -238,7 +238,6 @@ const Dataset = () => { }) } managePer={{ - mode: 'all', permission: folderDetail.permission, onGetCollaboratorList: () => getCollaboratorList(folderDetail._id), permissionList: DatasetPermissionList, diff --git a/projects/app/src/service/core/ai/apiproxy.d.ts b/projects/app/src/service/core/ai/apiproxy.d.ts new file mode 100644 index 000000000000..c0af5ff5e604 --- /dev/null +++ b/projects/app/src/service/core/ai/apiproxy.d.ts @@ -0,0 +1,5 @@ +export type CreateModelParams = { + name: string; + description: string; + prompt: string; +}; diff --git a/projects/app/src/service/core/ai/apiproxy.ts b/projects/app/src/service/core/ai/apiproxy.ts new file mode 100644 index 000000000000..3b6c3ccc9074 --- /dev/null +++ b/projects/app/src/service/core/ai/apiproxy.ts @@ -0,0 +1,66 @@ +import { addLog } from '@fastgpt/service/common/system/log'; +import axios, { Method } from 'axios'; + +const url = process.env.API_PROXY_URL; +const token = process.env.API_PROXY_TOKEN; + +const instance = axios.create({ + baseURL: url, + timeout: 60000, // 超时时间 + headers: { + Authorization: `Bearer ${token}` + } +}); + +/** + * 响应数据检查 + */ +const checkRes = (data: any) => { + if (data === undefined) { + addLog.info('api proxy data is empty'); + return Promise.reject('服务器异常'); + } + return data.data; +}; +const responseError = (err: any) => { + console.log('error->', '请求错误', err); + + if (!err) { + return Promise.reject({ message: '未知错误' }); + } + if (typeof err === 'string') { + return Promise.reject({ message: err }); + } + if (typeof err.message === 'string') { + return Promise.reject({ message: err.message }); + } + if (typeof err.data === 'string') { + return Promise.reject({ message: err.data }); + } + if (err?.response?.data) { + return Promise.reject(err?.response?.data); + } + return Promise.reject(err); +}; + +const request = (url: string, data: any, method: Method): Promise => { + /* 去空 */ + for (const key in data) { + if (data[key] === undefined) { + delete data[key]; + } + } + + return instance + .request({ + url, + method, + data: ['POST', 'PUT'].includes(method) ? data : undefined, + params: !['POST', 'PUT'].includes(method) ? data : undefined + }) + .then((res) => checkRes(res.data)) + .catch((err) => responseError(err)); +}; + +// TODO: channel crud +export const ApiProxy = {}; diff --git a/projects/app/src/web/common/api/request.ts b/projects/app/src/web/common/api/request.ts index 9ab8aa4d4ec5..009dd5c45066 100644 --- a/projects/app/src/web/common/api/request.ts +++ b/projects/app/src/web/common/api/request.ts @@ -9,6 +9,7 @@ import { TOKEN_ERROR_CODE } from '@fastgpt/global/common/error/errorCode'; import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; import { useSystemStore } from '../system/useSystemStore'; import { getWebReqUrl } from '@fastgpt/web/common/system/utils'; +import { i18nT } from '@fastgpt/web/i18n/utils'; interface ConfigType { headers?: { [key: string]: string }; @@ -108,17 +109,15 @@ function responseError(err: any) { return Promise.reject({ message: err }); } // 有报错响应 - if (err?.code in TOKEN_ERROR_CODE) { - if ( - !(window.location.pathname === '/chat/share' || window.location.pathname === '/chat/team') - ) { + if (err?.code in TOKEN_ERROR_CODE || err?.response?.data?.code in TOKEN_ERROR_CODE) { + if (!['/chat/share', '/chat/team', '/login'].includes(window.location.pathname)) { clearToken(); window.location.replace( getWebReqUrl(`/login?lastRoute=${encodeURIComponent(location.pathname + location.search)}`) ); } - return Promise.reject({ message: '无权操作' }); + return Promise.reject({ message: i18nT('common:unauth_token') }); } if ( err?.statusText === TeamErrEnum.aiPointsNotEnough || diff --git a/projects/app/src/web/support/user/team/api.ts b/projects/app/src/web/support/user/team/api.ts index 85aabfc91232..34e5759f973a 100644 --- a/projects/app/src/web/support/user/team/api.ts +++ b/projects/app/src/web/support/user/team/api.ts @@ -1,9 +1,8 @@ import { GET, POST, PUT, DELETE } from '@/web/common/api/request'; import { - CreatePermissionBody, + CollaboratorItemType, DeletePermissionQuery, - ListPermissionResponse, - UpdatePermissionBody + UpdateClbPermissionProps } from '@fastgpt/global/support/permission/collaborator'; import { CreateTeamProps, @@ -43,16 +42,11 @@ export const updateInviteResult = (data: UpdateInviteProps) => PUT('/proApi/support/user/team/member/updateInvite', data); export const delLeaveTeam = () => DELETE('/proApi/support/user/team/member/leave'); -export const getTeamClbs = () => - GET(`/proApi/support/user/team/collaborator/list`); - /* -------------- team collaborator -------------------- */ -export const updateMemberPermission = (data: UpdatePermissionBody) => - PUT('/proApi/support/user/team/collaborator/updatePermission', data); - -export const createMemberPermission = (data: CreatePermissionBody) => - POST('/proApi/support/user/team/collaborator/create', data); - +export const getTeamClbs = () => + GET(`/proApi/support/user/team/collaborator/list`); +export const updateMemberPermission = (data: UpdateClbPermissionProps) => + PUT('/proApi/support/user/team/collaborator/update', data); export const deleteMemberPermission = (id: DeletePermissionQuery) => DELETE('/proApi/support/user/team/collaborator/delete', id);