diff --git a/docSite/content/zh-cn/docs/development/upgrading/4818.md b/docSite/content/zh-cn/docs/development/upgrading/4818.md index b87c83fd4..e6665959c 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 83f13eb81..533ced31d 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 faa1d3948..af7ee84e1 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 000000000..4f6290f75 --- /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 000000000..0bcf7f6de --- /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 000000000..3cc1a33b5 --- /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 000000000..2d8639786 --- /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 000000000..80105c227 --- /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 bd1b764da..43e0db8aa 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 1b6a0767c..d944efba1 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 38cd50a39..9fde10ec4 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 b115fa506..58155816a 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 1dc0b0a58..f36a3ea8a 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 a76add2cc..9ee85b932 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 85b0e4954..5eae52e1f 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 2c8526087..7aca2c892 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 d0ea685aa..7eb979052 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 4b7b14ac4..47696b48d 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 395ebe1d2..59130d9da 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 a28aabc59..dc3f4d736 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 d8ec45aed..765d16439 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 90773bed3..0f39051c0 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 8798da50b..000000000 --- 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 e7e08c5f2..dd854713f 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 [selectedGroupIdList, setSelectedGroupIdList] = useState([]); - const [selectedOrgIdList, setSelectedOrgIdList] = useState([]); - const [selectedPermission, setSelectedPermission] = useState( - context?.permissionList['read'].value - ); - const perLabel = useMemo(() => { - if (context) return context.getPerLabelList(selectedPermission!).join('、'); - }, [context, selectedPermission]); + 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 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 (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 7d9f7de16..5bda6929c 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 dd13afafc..b46c6d786 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 000000000..2568c9500 --- /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 6af07f087..c0c1b5f50 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 dd7b95a46..ba25e90b6 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); + + {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: 'primary' as MenuItemType - }, - { - label: t('common:common.Delete'), - icon: 'delete', - onClick: () => { - openDeleteGroupModal(() => delDeleteGroup(group._id))(); - }, - 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 b42dc837d..08b9af2bc 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) => ( - - - - - - ))} - -
- {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 - }) - )(); - }} - /> - )} -
+ <> + + {Tabs} + + {userInfo?.team.permission.hasManagePer && feConfigs?.show_team_chat && ( + + )} + {userInfo?.team.permission.hasManagePer && ( + + )} + {!userInfo?.team.permission.isOwner && ( + + )} + + - -
-
+ + + + + + + + + + + + {members?.map((item) => ( + + + + + + ))} + +
+ {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 + }) + )(); + }} + /> + )} +
+ + +
+
+ + + {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 b4fadcc70..241fc1aee 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')} - {!!editOrg && ( - setEditOrg(undefined)} - onSuccess={refetchOrgs} - /> - )} - {!!movingOrg && ( - setMovingOrg(undefined)} - onSuccess={refetchOrgs} - /> - )} - {!!manageMemberOrg && ( - setManageMemberOrg(undefined)} - /> - )} + - - -
+ + {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)} + /> + )} + + + + + ); } 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 4c7b89bd7..cb97ba24c 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] - ); - - const orgs = useCreation( - () => - clbs.org.map((item) => ({ - ...item, - permission: new TeamPermission({ per: item.permission }) - })), - [clbs] - ); - - const { runAsync: onUpdateMemberPermission } = useRequest2(updateMemberPermission, { - onSuccess: () => { - refetchClbs(); - } - }); - - 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 - }); - } - } + collaboratorList.forEach((item) => { + if (item.tmbId) { + tmbList.push(item); + } else if (item.groupId) { + groupList.push(item); + } else if (item.orgId) { + orgList.push(item); } - 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 - }); - } - } + }); + + return { + tmbList, + groupList, + orgList + }; + }, [collaboratorList]); + + 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 + ); + + if (!clb) return; + + 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: 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 { 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) => ( - - + + + {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.group.members')} - - + {isExpandMember && + tmbList.map((member) => ( + + + + + {userManage && + !member.permission.isOwner && + userInfo?.team.tmbId !== member.tmbId && ( + + )} + + ))} + + + <> + + + + - - - + {isExpandOrg && + orgList.map((org) => ( + + + + + {userInfo?.permission.isOwner && ( + + )} + + ))} + + + <> + + + + - - - {userManage && - !member.permission.isOwner && - userInfo?.team.tmbId !== member.tmbId && ( - + {isExpandGroup && + groupList.map((group) => ( + + - )} - - ))} - - - - - - {t('user:team.org.org')} - - - - {isExpandOrg && - orgs.map((org) => ( - - - - - {userInfo?.permission.isOwner && ( - - )} - - ))} - - - - - - {t('user:team.group.group')} - - - - {isExpandGroup && - groups.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')} + +
- - - e.target.checked - ? onAddPermission({ memberId: String(member.tmbId), per: 'write' }) - : onRemovePermission({ memberId: String(member.tmbId), per: 'write' }) - } +
+ + + {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({ memberId: String(member.tmbId), per: 'manage' }) - : onRemovePermission({ memberId: String(member.tmbId), 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({ tmbId: String(member.tmbId) })} + {t('user:team.group.group')} + +
+ - -
- - - - - 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' }) - } - /> - - - - onDeleteMemberPermission({ orgId: org.orgId })} - /> - -
- - - - - 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' }) - } - /> - - - - onDeleteMemberPermission({ groupId: group.groupId })} - /> - -
- {isOpenAddPermission && ( - { - refetchClbs(); - onCloseAddPermission(); - }} - mode="all" - /> - )} -
+
+ + + 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 7ad30ad5a..b76ed99c3 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 772326870..6307d5c4b 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 000000000..feb458e53 --- /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 b058bbe8b..f82b1e931 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 d9165b2b0..313f2c456 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 72320796b..85d1f070d 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 c4ebf94c5..602652975 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 c201d70ea..766403767 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 d1412cc54..e3269664a 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 4700ef19c..3300437d3 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 0872e7bb4..a1f96c350 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 000000000..c0af5ff5e --- /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 000000000..3b6c3ccc9 --- /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 9ab8aa4d4..009dd5c45 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 85aabfc91..34e5759f9 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);