4.8.18 test (#3543)

* perf: login check

* doc

* perf: llm model config

* perf: team clb config
This commit is contained in:
Archer
2025-01-07 14:21:05 +08:00
committed by GitHub
parent 07cc849877
commit f556fbf0d5
49 changed files with 1644 additions and 1535 deletions

View File

@@ -10,10 +10,13 @@ weight: 806
## 完整更新内容 ## 完整更新内容
1. 1.
2. 新增 - 支持部门架构权限模式 2. 新增 - 支持部门架构权限模式
3. 优化 - 图片上传安全校验。并增加头像图片唯一存储,确保不会累计存储 3. 新增 - 支持配置自定跨域安全策略,默认全开
4. 优化 - Mongo 全文索引表分离 4. 优化 - 图片上传安全校验。并增加头像图片唯一存储,确保不会累计存储
5. 优化 - 知识库检索查询语句合并,同时减少查库数量 5. 优化 - Mongo 全文索引表分离
6. 优化 - 文件编码检测,减少 CSV 文件乱码概率 6. 优化 - 知识库检索查询语句合并,同时减少查库数量
7. 优化 - 异步读取文件内容,减少进程阻塞 7. 优化 - 文件编码检测,减少 CSV 文件乱码概率
8. 修复 - HTML 文件上传base64 图片无法自动转图片链接。 8. 优化 - 异步读取文件内容,减少进程阻塞。
9. 优化 - 文件阅读HTML 直接下载,不允许在线阅读。
10. 修复 - HTML 文件上传base64 图片无法自动转图片链接。
11. 修复 - 插件计费错误。

View File

@@ -53,6 +53,7 @@ export type VectorModelItemType = PriceType & {
}; };
export type ReRankModelItemType = PriceType & { export type ReRankModelItemType = PriceType & {
provider: ModelProviderIdType;
model: string; model: string;
name: string; name: string;
requestUrl: string; requestUrl: string;

View File

@@ -20,40 +20,8 @@ export type UpdateClbPermissionProps = {
permission: PermissionValueType; 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<{ export type DeletePermissionQuery = RequireOnlyOne<{
tmbId?: string; tmbId?: string;
groupId?: string; groupId?: string;
orgId?: 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 })[];
};

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -0,0 +1,6 @@
{
"provider": "BAAI",
"model": "bge-reranker-v2-m3",
"name": "bge-reranker-v2-m3",
"charsPointsPrice": 0
}

View File

@@ -0,0 +1,6 @@
{
"provider": "OpenAI",
"model": "whisper-1",
"name": "whisper-1",
"charsPointsPrice": 0
}

View File

@@ -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"
}
]
}

View File

@@ -5,11 +5,11 @@ import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
/* /*
Plugin points calculation: Plugin points calculation:
1. 商业版插件: 1. 系统插件/商业版插件:
- 有错误:返回 0 - 有错误:返回 0
- 无错误:返回 配置的点数 + 子节点点数 - 无错误:返回 单次积分 + 子流程积分(可配置)
2. 其他插件 2. 个人插件
- 返回 子节点点数 - 返回 子流程积分
*/ */
export const computedPluginUsage = async ({ export const computedPluginUsage = async ({
plugin, plugin,
@@ -26,9 +26,9 @@ export const computedPluginUsage = async ({
if (source !== PluginSourceEnum.personal) { if (source !== PluginSourceEnum.personal) {
if (error) return 0; 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; return childrenUsages;

View File

@@ -8,10 +8,7 @@ import { authOpenApiKey } from '../openapi/auth';
import { FileTokenQuery } from '@fastgpt/global/common/file/type'; import { FileTokenQuery } from '@fastgpt/global/common/file/type';
import { MongoResourcePermission } from './schema'; import { MongoResourcePermission } from './schema';
import { ClientSession } from 'mongoose'; import { ClientSession } from 'mongoose';
import { import { PermissionValueType } from '@fastgpt/global/support/permission/type';
PermissionValueType,
ResourcePermissionType
} from '@fastgpt/global/support/permission/type';
import { bucketNameMap } from '@fastgpt/global/common/file/constants'; import { bucketNameMap } from '@fastgpt/global/common/file/constants';
import { addMinutes } from 'date-fns'; import { addMinutes } from 'date-fns';
import { getGroupsByTmbId } from './memberGroup/controllers'; import { getGroupsByTmbId } from './memberGroup/controllers';
@@ -107,44 +104,6 @@ export const getResourcePermission = async ({
return concatPer([...groupPers, ...orgPers]); return concatPer([...groupPers, ...orgPers]);
}; };
/* 仅取 members 不取 groups */
export async function getResourceAllClbs({
resourceId,
teamId,
resourceType,
session
}: {
teamId: string;
session?: ClientSession;
} & (
| {
resourceType: 'team';
resourceId?: undefined;
}
| {
resourceType: Omit<PerResourceTypeEnum, 'team'>;
resourceId?: string | null;
}
)): Promise<ResourcePermissionType[]> {
return MongoResourcePermission.find(
{
resourceType: resourceType,
teamId: teamId,
resourceId,
groupId: {
$exists: false
},
orgId: {
$exists: false
}
},
null,
{
session
}
).lean();
}
export async function getResourceClbsAndGroups({ export async function getResourceClbsAndGroups({
resourceId, resourceId,
resourceType, resourceType,
@@ -172,10 +131,17 @@ export const getClbsAndGroupsWithInfo = async ({
resourceType, resourceType,
teamId teamId
}: { }: {
resourceId: ParentIdType;
resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>;
teamId: string; teamId: string;
}) => } & (
| {
resourceId: ParentIdType;
resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>;
}
| {
resourceType: 'team';
resourceId?: undefined;
}
)) =>
Promise.all([ Promise.all([
MongoResourcePermission.find({ MongoResourcePermission.find({
teamId, teamId,

View File

@@ -18,7 +18,6 @@ const MyIconButton = ({
}: Props) => { }: Props) => {
return ( return (
<Flex <Flex
mr={1}
p={1} p={1}
color={'myGray.500'} color={'myGray.500'}
rounded={'sm'} rounded={'sm'}

View File

@@ -1,15 +1,15 @@
import React from 'react'; import React, { forwardRef } from 'react';
import { Flex, Box, BoxProps } from '@chakra-ui/react'; import { Flex, Box, BoxProps } from '@chakra-ui/react';
import MyIcon from '../Icon'; import MyIcon from '../Icon';
type Props = Omit<BoxProps, 'onChange'> & { type Props<T = string> = Omit<BoxProps, 'onChange'> & {
list: { list: {
icon?: string; icon?: string;
label: string | React.ReactNode; label: string | React.ReactNode;
value: string; value: T;
}[]; }[];
value: string; value: T;
onChange: (e: string) => void; onChange: (e: T) => void;
}; };
const FillRowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props }: Props) => { 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 <T>(
props: Props<T> & { ref?: React.Ref<HTMLSelectElement> }
) => JSX.Element;

View File

@@ -1,7 +1,14 @@
{ {
"active_model": "Available models",
"add_default_model": "Add a preset model",
"api_key": "API key", "api_key": "API key",
"bills_and_invoices": "Bills", "bills_and_invoices": "Bills",
"channel": "Channel",
"confirm_logout": "Confirm to log out?", "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", "logout": "Sign out",
"model_provider": "Model Provider", "model_provider": "Model Provider",
"notifications": "Notify", "notifications": "Notify",

View File

@@ -1187,6 +1187,7 @@
"tag_list": "Tag List", "tag_list": "Tag List",
"team_tag": "Team Tag", "team_tag": "Team Tag",
"textarea_variable_picker_tip": "Enter \"/\" to select a variable", "textarea_variable_picker_tip": "Enter \"/\" to select a variable",
"unauth_token": "The certificate has expired, please log in again",
"unit.character": "Character", "unit.character": "Character",
"unit.minute": "Minute", "unit.minute": "Minute",
"unit.seconds": "Second", "unit.seconds": "Second",

View File

@@ -49,7 +49,7 @@
"execution_error": "Execution Error", "execution_error": "Execution Error",
"extraction_requirements_description": "Extraction Requirements Description", "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_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", "feedback_text": "Feedback Text",
"field_description": "Field Description", "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.", "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.",

View File

@@ -1,7 +1,14 @@
{ {
"active_model": "可用模型",
"add_default_model": "添加预设模型",
"api_key": "API 密钥", "api_key": "API 密钥",
"bills_and_invoices": "账单与发票", "bills_and_invoices": "账单与发票",
"channel": "渠道",
"confirm_logout": "确认退出登录?", "confirm_logout": "确认退出登录?",
"create_channel": "新增渠道",
"create_model": "新增模型",
"custom_model": "自定义模型",
"default_model": "预设模型",
"logout": "登出", "logout": "登出",
"model_provider": "模型提供商", "model_provider": "模型提供商",
"notifications": "通知", "notifications": "通知",

View File

@@ -1190,6 +1190,7 @@
"tag_list": "标签列表", "tag_list": "标签列表",
"team_tag": "团队标签", "team_tag": "团队标签",
"textarea_variable_picker_tip": "输入\"/\"可选择变量", "textarea_variable_picker_tip": "输入\"/\"可选择变量",
"unauth_token": "凭证已过期,请重新登录",
"unit.character": "字符", "unit.character": "字符",
"unit.minute": "分钟", "unit.minute": "分钟",
"unit.seconds": "秒", "unit.seconds": "秒",

View File

@@ -106,7 +106,7 @@
"team.group.set_as_admin": "设为管理员", "team.group.set_as_admin": "设为管理员",
"team.group.toast.can_not_delete_owner": "不能删除所有者, 请先转让", "team.group.toast.can_not_delete_owner": "不能删除所有者, 请先转让",
"team.group.transfer_owner": "转让所有者", "team.group.transfer_owner": "转让所有者",
"team.org.org": "组织", "team.org.org": "部门",
"team.manage_collaborators": "管理协作者", "team.manage_collaborators": "管理协作者",
"team.no_collaborators": "暂无协作者", "team.no_collaborators": "暂无协作者",
"team.write_role_member": "可写权限", "team.write_role_member": "可写权限",

View File

@@ -49,7 +49,7 @@
"execution_error": "运行错误", "execution_error": "运行错误",
"extraction_requirements_description": "提取要求描述", "extraction_requirements_description": "提取要求描述",
"extraction_requirements_description_detail": "给AI一些对应的背景知识或要求描述引导AI更好的完成任务。\\n该输入框可使用全局变量。", "extraction_requirements_description_detail": "给AI一些对应的背景知识或要求描述引导AI更好的完成任务。\\n该输入框可使用全局变量。",
"extraction_requirements_placeholder": "例如: \\n1. 当前时间为: {{cTime}}。你是一个实验室预约助手,你的任务是帮助用户预约实验室,从文本中获取对应的预约信息。\\n2. 你是谷歌搜索助手,需要从文本中提取出合适的搜索词。", "extraction_requirements_placeholder": "例如: 1. 当前时间为: {{cTime}}。你是一个实验室预约助手,你的任务是帮助用户预约实验室,从文本中获取对应的预约信息。\n2. 你是谷歌搜索助手,需要从文本中提取出合适的搜索词。",
"feedback_text": "反馈的文本", "feedback_text": "反馈的文本",
"field_description": "字段描述", "field_description": "字段描述",
"field_description_placeholder": "描述该输入字段的功能,如果为工具调用参数,则该描述会影响模型生成的质量", "field_description_placeholder": "描述该输入字段的功能,如果为工具调用参数,则该描述会影响模型生成的质量",

View File

@@ -1,7 +1,14 @@
{ {
"active_model": "可用模型",
"add_default_model": "新增預設模型",
"api_key": "API 金鑰", "api_key": "API 金鑰",
"bills_and_invoices": "帳單與發票", "bills_and_invoices": "帳單與發票",
"channel": "頻道",
"confirm_logout": "確認登出登入?", "confirm_logout": "確認登出登入?",
"create_channel": "新增頻道",
"create_model": "新增模型",
"custom_model": "自訂模型",
"default_model": "預設模型",
"logout": "登出", "logout": "登出",
"model_provider": "模型提供者", "model_provider": "模型提供者",
"notifications": "通知", "notifications": "通知",

View File

@@ -1187,6 +1187,7 @@
"tag_list": "標籤列表", "tag_list": "標籤列表",
"team_tag": "團隊標籤", "team_tag": "團隊標籤",
"textarea_variable_picker_tip": "輸入「/」以選擇變數", "textarea_variable_picker_tip": "輸入「/」以選擇變數",
"unauth_token": "憑證已過期,請重新登入",
"unit.character": "字元", "unit.character": "字元",
"unit.minute": "分鐘", "unit.minute": "分鐘",
"unit.seconds": "秒", "unit.seconds": "秒",

View File

@@ -49,7 +49,7 @@
"execution_error": "執行錯誤", "execution_error": "執行錯誤",
"extraction_requirements_description": "擷取需求描述", "extraction_requirements_description": "擷取需求描述",
"extraction_requirements_description_detail": "提供 AI 相對應的背景知識或需求描述,引導 AI 更好地完成任務。\\n這個輸入框可以使用全域變數。", "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": "回饋文字", "feedback_text": "回饋文字",
"field_description": "欄位描述", "field_description": "欄位描述",
"field_description_placeholder": "描述這個輸入欄位的功能,如果是工具呼叫參數,這個描述會影響模型產生的品質", "field_description_placeholder": "描述這個輸入欄位的功能,如果是工具呼叫參數,這個描述會影響模型產生的品質",

View File

@@ -7,7 +7,6 @@ import MyDivider from '@fastgpt/web/components/common/MyDivider';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { PermissionValueType } from '@fastgpt/global/support/permission/type'; import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import DefaultPermissionList from '@/components/support/permission/DefaultPerList';
import CollaboratorContextProvider, { import CollaboratorContextProvider, {
MemberManagerInputPropsType MemberManagerInputPropsType
} from '../../support/permission/MemberManager/context'; } from '../../support/permission/MemberManager/context';
@@ -24,7 +23,6 @@ const FolderSlideCard = ({
deleteTip, deleteTip,
onDelete, onDelete,
defaultPer,
managePer, managePer,
isInheritPermission, isInheritPermission,
resumeInheritPermission, resumeInheritPermission,
@@ -39,11 +37,6 @@ const FolderSlideCard = ({
deleteTip: string; deleteTip: string;
onDelete: () => void; onDelete: () => void;
defaultPer?: {
value: PermissionValueType;
defaultValue: PermissionValueType;
onChange: (v: PermissionValueType) => Promise<any>;
};
managePer: MemberManagerInputPropsType; managePer: MemberManagerInputPropsType;
isInheritPermission?: boolean; isInheritPermission?: boolean;

View File

@@ -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 <MemberModal onClose={onClose} mode={mode} collaboratorContext={context} />;
}
export default AddMemberModal;

View File

@@ -19,35 +19,31 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
import MyModal from '@fastgpt/web/components/common/MyModal'; import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useMemo, useState } from 'react'; import { useMemo, useRef, useState } from 'react';
import { useContextSelector } from 'use-context-selector';
import PermissionSelect from './PermissionSelect'; import PermissionSelect from './PermissionSelect';
import PermissionTags from './PermissionTags'; import PermissionTags from './PermissionTags';
import { MemberManagerPropsType } from './context';
import { DEFAULT_ORG_AVATAR, DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants'; import { DEFAULT_ORG_AVATAR, DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants';
import Path from '@/components/common/folder/Path'; import Path from '@/components/common/folder/Path';
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant'; import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import { OrgType } from '@fastgpt/global/support/user/team/org/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 = { const HoverBoxStyle = {
onClose: () => void; bgColor: 'myGray.50',
mode?: 'member' | 'all'; cursor: 'pointer'
}; };
function MemberModal({ function MemberModal({ onClose }: { onClose: () => void }) {
onClose,
mode = 'member',
collaboratorContext: context
}: AddModalPropsType & { collaboratorContext?: MemberManagerPropsType }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, myGroups, loadAndGetOrgs } = const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, myGroups, loadAndGetOrgs } =
useUserStore(); useUserStore();
const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList);
const [searchText, setSearchText] = useState<string>(''); const [searchText, setSearchText] = useState<string>('');
const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>(); const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>();
const [parentPath, setParentPath] = useState('');
const { data: [members = [], groups = [], orgs = []] = [], loading: loadingMembersAndGroups } = const { data: [members = [], groups = [], orgs = []] = [], loading: loadingMembersAndGroups } =
useRequest2( useRequest2(
@@ -65,13 +61,7 @@ function MemberModal({
} }
); );
const currentOrg = useMemo(() => { const [parentPath, setParentPath] = useState('');
const splitPath = parentPath.split('/');
const currentOrgId = splitPath[splitPath.length - 1];
if (!currentOrgId) return;
return orgs.find((org) => org.pathId === currentOrgId);
}, [orgs, parentPath]);
const paths = useMemo(() => { const paths = useMemo(() => {
const splitPath = parentPath.split('/').filter(Boolean); const splitPath = parentPath.split('/').filter(Boolean);
return splitPath return splitPath
@@ -88,30 +78,17 @@ function MemberModal({
.filter(Boolean) as ParentTreePathItemType[]; .filter(Boolean) as ParentTreePathItemType[];
}, [parentPath, orgs]); }, [parentPath, orgs]);
const filterMembers = useMemo(() => { const [selectedOrgIdList, setSelectedOrgIdList] = useState<string[]>([]);
if (!searchText && filterClass !== 'member' && filterClass !== 'org') return []; const currentOrg = useMemo(() => {
if (searchText) return members.filter((item) => item.memberName.includes(searchText)); const splitPath = parentPath.split('/');
if (filterClass === 'org') { const currentOrgId = splitPath[splitPath.length - 1];
if (!currentOrg) return []; if (!currentOrgId) 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]);
return orgs.find((org) => org.pathId === currentOrgId);
}, [orgs, parentPath]);
const filterOrgs: (OrgType & { count?: number })[] = useMemo(() => { 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) return orgs.filter((item) => item.name.includes(searchText));
if (!searchText && filterClass !== 'org') return [];
if (parentPath === '') { if (parentPath === '') {
setParentPath(`/${orgs[0].pathId}`); setParentPath(`/${orgs[0].pathId}`);
return []; return [];
@@ -123,44 +100,97 @@ function MemberModal({
count: count:
item.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(item)).length item.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(item)).length
})); }));
}, [orgs, searchText, filterClass, mode, parentPath]); }, [orgs, searchText, filterClass, parentPath]);
const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]); const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]);
const [selectedGroupIdList, setSelectedGroupIdList] = useState<string[]>([]); const filterMembers = useMemo(() => {
const [selectedOrgIdList, setSelectedOrgIdList] = useState<string[]>([]); if (searchText) return members.filter((item) => item.memberName.includes(searchText));
const [selectedPermission, setSelectedPermission] = useState( if (!searchText && filterClass !== 'member' && filterClass !== 'org') return [];
context?.permissionList['read'].value if (filterClass === 'org') {
); if (!currentOrg) return [];
const perLabel = useMemo(() => { return members.filter((item) => currentOrg.members.find((v) => v.tmbId === item.tmbId));
if (context) return context.getPerLabelList(selectedPermission!).join('、'); }
}, [context, selectedPermission]); return members;
}, [members, searchText, filterClass, currentOrg]);
const [selectedGroupIdList, setSelectedGroupIdList] = useState<string[]>([]);
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<number>();
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( const { runAsync: onConfirm, loading: isUpdating } = useRequest2(
() => { () =>
if (context) { onUpdateCollaborators({
return context.onUpdateCollaborators({ members: selectedMemberIdList,
members: selectedMemberIdList, groups: selectedGroupIdList,
groups: selectedGroupIdList, orgs: selectedOrgIdList,
orgs: selectedOrgIdList, permission: selectedPermission!
permission: selectedPermission! }),
});
} else {
return createMemberPermission({
tmbId: selectedMemberIdList,
groupId: selectedGroupIdList,
orgId: selectedOrgIdList
});
}
},
{ {
successToast: t('common:common.Add Success'), successToast: t('common:common.Add Success'),
errorToast: 'Error',
onSuccess() { onSuccess() {
onClose(); 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 ( return (
<MyModal <MyModal
isOpen isOpen
@@ -169,7 +199,7 @@ function MemberModal({
title={t('user:team.add_collaborator')} title={t('user:team.add_collaborator')}
minW="800px" minW="800px"
h={'100%'} h={'100%'}
maxH={'800px'} maxH={'90vh'}
isCentered isCentered
isLoading={loadingMembersAndGroups} isLoading={loadingMembersAndGroups}
> >
@@ -181,73 +211,49 @@ function MemberModal({
gridTemplateColumns="1fr 1fr" gridTemplateColumns="1fr 1fr"
h={'100%'} h={'100%'}
> >
<Flex flexDirection="column" borderRight="1px solid" borderColor="myGray.200" p="4"> <Flex
h={'100%'}
flexDirection="column"
borderRight="1px solid"
borderColor="myGray.200"
p="4"
>
<SearchInput <SearchInput
placeholder={t('user:search_user')} placeholder={t('user:search_user')}
bgColor="myGray.50" bgColor="myGray.50"
onChange={(e) => setSearchText(e.target.value)} onChange={(e) => setSearchText(e.target.value)}
/> />
<Flex flexDirection="column" mt="2" overflow={'auto'} maxH="400px"> <Flex flexDirection="column" mt="3" overflow={'auto'} flex={'1 0 0'} h={0}>
{!searchText && {!searchText && !filterClass && (
(filterClass === undefined ? ( <>
<> {entryList.current.map((item) => {
<HStack return (
justifyContent="space-between" <HStack
py="2" key={item.value}
px="3" justifyContent="space-between"
borderRadius="sm" py="2"
alignItems="center" px="3"
_hover={{ borderRadius="sm"
bgColor: 'myGray.50', alignItems="center"
cursor: 'pointer' _hover={HoverBoxStyle}
}} _notLast={{ mb: 1 }}
onClick={() => setFilterClass('member')} onClick={() => setFilterClass(item.value as any)}
> >
<MyAvatar src="/imgs/avatar/BlueAvatar.svg" w="1.5rem" borderRadius={'50%'} /> <MyAvatar src={item.icon} w="1.5rem" borderRadius={'50%'} />
<Box ml="2" w="full"> <Box ml="2" w="full">
{t('user:team.group.members')} {item.label}
</Box> </Box>
<MyIcon name="core/chat/chevronRight" w="16px" /> <MyIcon name="core/chat/chevronRight" w="16px" />
</HStack> </HStack>
<HStack );
justifyContent="space-between" })}
py="2" </>
px="3" )}
borderRadius="sm"
alignItems="center" {/* Path */}
_hover={{ {!searchText && filterClass && (
bgColor: 'myGray.50', <Box mb={1}>
cursor: 'pointer'
}}
onClick={() => setFilterClass('org')}
>
<MyAvatar src={DEFAULT_ORG_AVATAR} w="1.5rem" borderRadius={'50%'} />
<Box ml="2" w="full">
{t('user:team.org.org')}
</Box>
<MyIcon name="core/chat/chevronRight" w="16px" />
</HStack>
<HStack
justifyContent="space-between"
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer'
}}
onClick={() => setFilterClass('group')}
>
<MyAvatar src={DEFAULT_TEAM_AVATAR} w="1.5rem" borderRadius={'50%'} />
<Box ml="2" w="full">
{t('user:team.group.group')}
</Box>
<MyIcon name="core/chat/chevronRight" w="16px" />
</HStack>
</>
) : (
<Path <Path
paths={[ paths={[
{ {
@@ -278,267 +284,173 @@ function MemberModal({
}} }}
rootName={t('common:common.Team')} rootName={t('common:common.Team')}
/> />
))} </Box>
{filterOrgs.map((org) => { )}
const onChange = () => {
setSelectedOrgIdList((state) => { <Flex flexDirection={'column'} gap={1} userSelect={'none'}>
if (state.includes(org._id)) { {filterMembers.map((member) => {
return state.filter((v) => v !== org._id); const onChange = () => {
} setSelectedMembers((state) => {
return [...state, org._id]; if (state.includes(member.tmbId)) {
}); return state.filter((v) => v !== member.tmbId);
}; }
const collaborator = context?.collaboratorList?.find((v) => v.orgId === org._id); return [...state, member.tmbId];
return ( });
<HStack };
justifyContent="space-between" const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId);
key={org._id} return (
py="2" <HStack
px="3" justifyContent="space-between"
borderRadius="sm" key={member.tmbId}
alignItems="center" py="2"
_hover={{ px="3"
bgColor: 'myGray.50', borderRadius="sm"
cursor: 'pointer' alignItems="center"
}} _hover={HoverBoxStyle}
onClick={onChange} onClick={onChange}
> >
<Checkbox <Checkbox
isChecked={selectedOrgIdList.includes(org._id)} isChecked={selectedMemberIdList.includes(member.tmbId)}
pointerEvents="none" pointerEvents="none"
/> />
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} /> <MyAvatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
<HStack ml="2" w="full" gap="5px"> <Box w="full" ml="2">
<Text>{org.name}</Text> {member.memberName}
</Box>
<PermissionTags permission={collaborator?.permission.value} />
</HStack>
);
})}
{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 (
<HStack
justifyContent="space-between"
key={org._id}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={HoverBoxStyle}
onClick={onChange}
>
<Checkbox
isChecked={selectedOrgIdList.includes(org._id)}
pointerEvents="none"
/>
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
<HStack ml="2" w="full" gap="5px">
<Text>{org.name}</Text>
{org.count && (
<>
<Tag size="sm" my="auto">
{org.count}
</Tag>
</>
)}
</HStack>
<PermissionTags permission={collaborator?.permission.value} />
{org.count && ( {org.count && (
<> <MyIcon
<Tag size="sm" my="auto"> name="core/chat/chevronRight"
{org.count} w="16px"
</Tag> p="4px"
</> rounded={'6px'}
_hover={{
bgColor: 'myGray.200'
}}
onClick={() => {
setParentPath(getOrgChildrenPath(org));
}}
/>
)} )}
</HStack> </HStack>
{!!collaborator && ( );
<PermissionTags permission={collaborator.permission.value} /> })}
)} {filterGroups.map((group) => {
{org.count && ( const onChange = () => {
<MyIcon setSelectedGroupIdList((state) => {
name="core/chat/chevronRight" if (state.includes(group._id)) {
w="16px" return state.filter((v) => v !== group._id);
p="4px" }
rounded={'6px'} return [...state, group._id];
_hover={{ });
bgColor: 'myGray.200' };
}} const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
onClick={() => { return (
setParentPath(getOrgChildrenPath(org)); <HStack
}} justifyContent="space-between"
key={group._id}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={HoverBoxStyle}
onClick={onChange}
>
<Checkbox
isChecked={selectedGroupIdList.includes(group._id)}
pointerEvents="none"
/> />
)} <MyAvatar src={group.avatar} w="1.5rem" borderRadius={'50%'} />
</HStack> <Box ml="2" w="full">
); {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name}
})} </Box>
{filterGroups.map((group) => { <PermissionTags permission={collaborator?.permission.value} />
const onChange = () => { </HStack>
setSelectedGroupIdList((state) => { );
if (state.includes(group._id)) { })}
return state.filter((v) => v !== group._id); </Flex>
}
return [...state, group._id];
});
};
const collaborator = context?.collaboratorList?.find(
(v) => v.groupId === group._id
);
return (
<HStack
justifyContent="space-between"
key={group._id}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer'
}}
onClick={onChange}
>
<Checkbox
isChecked={selectedGroupIdList.includes(group._id)}
pointerEvents="none"
/>
<MyAvatar src={group.avatar} w="1.5rem" borderRadius={'50%'} />
<Box ml="2" w="full">
{group.name === DefaultGroupName ? userInfo?.team.teamName : group.name}
</Box>
{!!collaborator && (
<PermissionTags permission={collaborator.permission.value} />
)}
</HStack>
);
})}
{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 (
<HStack
justifyContent="space-between"
key={member.tmbId}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer'
}}
onClick={userInfo?.team?.tmbId === member.tmbId ? undefined : onChange}
>
<Checkbox
isChecked={selectedMemberIdList.includes(member.tmbId)}
pointerEvents="none"
isDisabled={userInfo?.team?.tmbId === member.tmbId}
/>
<MyAvatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
<Box w="full" ml="2">
{member.memberName}
</Box>
{!!collaborator && (
<PermissionTags permission={collaborator.permission.value} />
)}
</HStack>
);
})}
</Flex> </Flex>
</Flex> </Flex>
<Flex p="4" flexDirection="column">
<Flex h={'100%'} p="4" flexDirection="column">
<Box> <Box>
{`${t('user:has_chosen')}: `} {`${t('user:has_chosen')}: `}
{selectedMemberIdList.length + selectedGroupIdList.length + selectedOrgIdList.length} {selectedMemberIdList.length + selectedGroupIdList.length + selectedOrgIdList.length}
</Box> </Box>
<Flex flexDirection="column" mt="2" overflow={'auto'} maxH="400px"> <Flex flexDirection="column" mt="2" gap={1} overflow={'auto'} flex={'1 0 0'} h={0}>
{selectedOrgIdList.map((orgId) => { {selectedList.map((item) => {
const org = orgs.find((v) => String(v._id) === orgId);
return ( return (
<HStack <HStack
justifyContent="space-between" justifyContent="space-between"
key={orgId} key={item.id}
py="2" py="2"
px="3" px="3"
borderRadius="sm" borderRadius="sm"
alignItems="center" alignItems="center"
_hover={{ _hover={HoverBoxStyle}
bgColor: 'myGray.50',
cursor: 'pointer',
...(!selectedOrgIdList.includes(orgId) ? { svg: { color: 'myGray.50' } } : {})
}}
onClick={() =>
setSelectedOrgIdList(selectedOrgIdList.filter((v) => v !== orgId))
}
> >
<MyAvatar src={org?.avatar} w="1.5rem" borderRadius={'50%'} /> <MyAvatar src={item.avatar} w="1.5rem" borderRadius={'50%'} />
<Box w="full" ml="2"> <Box w="full" ml="2">
{org?.name} {item.name}
</Box> </Box>
<MyIcon <MyIcon
name="common/closeLight" name="common/closeLight"
w="16px" w="1rem"
cursor={'pointer'} cursor={'pointer'}
_hover={{ _hover={{
color: 'red.600' color: 'red.600'
}} }}
onClick={item.onDelete}
/> />
</HStack> </HStack>
); );
})} })}
{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 (
<HStack
justifyContent="space-between"
key={groupId}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer',
...(!selectedGroupIdList.includes(groupId)
? { svg: { color: 'myGray.50' } }
: {})
}}
onClick={onChange}
>
<MyAvatar src={group?.avatar} w="1.5rem" borderRadius={'50%'} />
<Box w="full" ml="2">
{group?.name === DefaultGroupName ? userInfo?.team.teamName : group?.name}
</Box>
<MyIcon
name="common/closeLight"
w="16px"
cursor={'pointer'}
_hover={{
color: 'red.600'
}}
/>
</HStack>
);
})}
{selectedMemberIdList.map((tmbId) => {
const member = members.find((v) => v.tmbId === tmbId);
return member ? (
<HStack
justifyContent="space-between"
key={tmbId}
alignItems="center"
py="2"
px={3}
borderRadius={'md'}
_hover={{ bg: 'myGray.50' }}
onClick={() =>
setSelectedMembers(selectedMemberIdList.filter((v) => v !== tmbId))
}
>
<MyAvatar src={member.avatar} w="1.5rem" borderRadius="50%" />
<Box w="full" ml={2}>
{member.memberName}
</Box>
<MyIcon
name="common/closeLight"
w="16px"
cursor={'pointer'}
_hover={{
color: 'red.600'
}}
/>
</HStack>
) : null;
})}
</Flex> </Flex>
</Flex> </Flex>
</Grid> </Grid>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
{selectedPermission && ( {!!permissionList && (
<PermissionSelect <PermissionSelect
value={selectedPermission} value={selectedPermission}
Button={ Button={

View File

@@ -49,13 +49,16 @@ function PermissionSelect({
onDelete onDelete
}: PermissionSelectProps) { }: PermissionSelectProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const { permission, permissionList } = useContextSelector(CollaboratorContext, (v) => v);
const ref = useRef<HTMLButtonElement>(null); const ref = useRef<HTMLButtonElement>(null);
const closeTimer = useRef<NodeJS.Timeout>(); const closeTimer = useRef<NodeJS.Timeout>();
const { permission, permissionList } = useContextSelector(CollaboratorContext, (v) => v);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const permissionSelectList = useMemo(() => { const permissionSelectList = useMemo(() => {
if (!permissionList) return { singleCheckBoxList: [], multipleCheckBoxList: [] };
const list = Object.entries(permissionList).map(([_, value]) => { const list = Object.entries(permissionList).map(([_, value]) => {
return { return {
name: value.name, name: value.name,
@@ -77,6 +80,8 @@ function PermissionSelect({
}; };
}, [permission.isOwner, permissionList]); }, [permission.isOwner, permissionList]);
const selectedSingleValue = useMemo(() => { const selectedSingleValue = useMemo(() => {
if (!permissionList) return undefined;
const per = new Permission({ per: value }); const per = new Permission({ per: value });
if (per.hasManagePer) return permissionList['manage'].value; if (per.hasManagePer) return permissionList['manage'].value;
@@ -107,7 +112,7 @@ function PermissionSelect({
} }
}); });
return ( return selectedSingleValue !== undefined ? (
<Menu offset={offset} isOpen={isOpen} autoSelect={false} direction={'ltr'}> <Menu offset={offset} isOpen={isOpen} autoSelect={false} direction={'ltr'}>
<Box <Box
w="fit-content" w="fit-content"
@@ -241,7 +246,7 @@ function PermissionSelect({
</MenuList> </MenuList>
</Box> </Box>
</Menu> </Menu>
); ) : null;
} }
export default React.memo(PermissionSelect); export default React.memo(PermissionSelect);

View File

@@ -7,12 +7,15 @@ import { CollaboratorContext } from './context';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
export type PermissionTagsProp = { export type PermissionTagsProp = {
permission: PermissionValueType; permission?: PermissionValueType;
}; };
function PermissionTags({ permission }: PermissionTagsProp) { function PermissionTags({ permission }: PermissionTagsProp) {
const { getPerLabelList } = useContextSelector(CollaboratorContext, (v) => v); const { getPerLabelList } = useContextSelector(CollaboratorContext, (v) => v);
const { t } = useTranslation(); const { t } = useTranslation();
if (permission === undefined) return null;
const perTagList = getPerLabelList(permission); const perTagList = getPerLabelList(permission);
return ( return (

View File

@@ -19,19 +19,19 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useI18n } from '@/web/context/I18n'; import { useI18n } from '@/web/context/I18n';
import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
const AddMemberModal = dynamic(() => import('./AddMemberModal'));
const MemberModal = dynamic(() => import('./MemberModal'));
const ManageModal = dynamic(() => import('./ManageModal')); const ManageModal = dynamic(() => import('./ManageModal'));
export type MemberManagerInputPropsType = { export type MemberManagerInputPropsType = {
permission: Permission; permission: Permission;
onGetCollaboratorList: () => Promise<CollaboratorItemType[]>; onGetCollaboratorList: () => Promise<CollaboratorItemType[]>;
permissionList: PermissionListType; permissionList?: PermissionListType;
onUpdateCollaborators: (props: UpdateClbPermissionProps) => Promise<any>; onUpdateCollaborators: (props: UpdateClbPermissionProps) => Promise<any>;
onDelOneCollaborator: ( onDelOneCollaborator: (
props: RequireOnlyOne<{ tmbId: string; groupId: string; orgId: string }> props: RequireOnlyOne<{ tmbId: string; groupId: string; orgId: string }>
) => Promise<any>; ) => Promise<any>;
refreshDeps?: any[]; refreshDeps?: any[];
mode?: 'member' | 'all';
}; };
export type MemberManagerPropsType = MemberManagerInputPropsType & { export type MemberManagerPropsType = MemberManagerInputPropsType & {
@@ -80,8 +80,7 @@ const CollaboratorContextProvider = ({
refetchResource, refetchResource,
refreshDeps = [], refreshDeps = [],
isInheritPermission, isInheritPermission,
hasParent, hasParent
mode = 'member'
}: MemberManagerInputPropsType & { }: MemberManagerInputPropsType & {
children: (props: ChildrenProps) => ReactNode; children: (props: ChildrenProps) => ReactNode;
refetchResource?: () => void; refetchResource?: () => void;
@@ -121,6 +120,8 @@ const CollaboratorContextProvider = ({
const getPerLabelList = useCallback( const getPerLabelList = useCallback(
(per: PermissionValueType) => { (per: PermissionValueType) => {
if (!permissionList) return [];
const Per = new Permission({ per }); const Per = new Permission({ per });
const labels: string[] = []; const labels: string[] = [];
@@ -128,7 +129,7 @@ const CollaboratorContextProvider = ({
labels.push(permissionList['manage'].name); labels.push(permissionList['manage'].name);
} else if (Per.hasWritePer) { } else if (Per.hasWritePer) {
labels.push(permissionList['write'].name); labels.push(permissionList['write'].name);
} else { } else if (Per.hasReadPer) {
labels.push(permissionList['read'].name); labels.push(permissionList['read'].name);
} }
@@ -203,12 +204,11 @@ const CollaboratorContextProvider = ({
MemberListCard MemberListCard
})} })}
{isOpenAddMember && ( {isOpenAddMember && (
<AddMemberModal <MemberModal
onClose={() => { onClose={() => {
onCloseAddMember(); onCloseAddMember();
refetchResource?.(); refetchResource?.();
}} }}
mode={mode}
/> />
)} )}
{isOpenManageModal && ( {isOpenManageModal && (

View File

@@ -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<string[]>([]);
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: (
<Flex alignItems={'center'} py={1}>
<Avatar
borderRadius={'0'}
mr={2}
src={provider?.avatar || HUGGING_FACE_ICON}
fallbackSrc={HUGGING_FACE_ICON}
w={'1rem'}
/>
<Box>{t(provider.name as any)}</Box>
</Flex>
),
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 (
<MyModal
isOpen
title={t('account:add_default_model')}
iconSrc="common/model"
iconColor="primary.600"
onClose={onClose}
>
<ModalBody>11</ModalBody>
</MyModal>
);
};
export default DefaultModal;

View File

@@ -1,15 +1,72 @@
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs'; import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import React from 'react'; import React, { useState } from 'react';
import AccountContainer from '../components/AccountContainer'; 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 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 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 ( return (
<AccountContainer> <AccountContainer>
<Box h={'100%'} py={4} px={6}> <Flex h={'100%'} flexDirection={'column'} gap={4} py={4} px={6}>
<ModelTable /> {/* Header */}
</Box> {/* <Flex justifyContent={'space-between'}>
<FillRowTabs<'model' | 'channel'>
list={[
{ label: t('account:active_model'), value: 'model' },
{ label: t('account:channel'), value: 'channel' }
]}
value={tab}
px={8}
py={1}
onChange={setTab}
/>
{tab === 'model' && (
<MyMenu
trigger="hover"
size="mini"
Button={<Button>{t('account:create_model')}</Button>}
menuList={[
{
children: [
{
label: t('account:default_model'),
onClick: onOpenDefault
},
{
label: t('account:custom_model')
}
]
}
]}
/>
)}
{tab === 'channel' && <Button>{t('account:create_channel')}</Button>}
</Flex> */}
<Box flex={'1 0 0'}>
{tab === 'model' && <ModelTable />}
{/* {tab === 'channel' && <ChannelTable />} */}
</Box>
</Flex>
{isOpenDefault && <DefaultModal onClose={onCloseDefault} />}
</AccountContainer> </AccountContainer>
); );
}; };

View File

@@ -1,6 +1,8 @@
import AvatarGroup from '@fastgpt/web/components/common/Avatar/AvatarGroup'; import AvatarGroup from '@fastgpt/web/components/common/Avatar/AvatarGroup';
import { import {
Box, Box,
Button,
Flex,
HStack, HStack,
Table, Table,
TableContainer, TableContainer,
@@ -29,17 +31,32 @@ import { useState } from 'react';
import IconButton from '../OrgManage/IconButton'; import IconButton from '../OrgManage/IconButton';
const ChangeOwnerModal = dynamic(() => import('./GroupTransferOwnerModal')); const ChangeOwnerModal = dynamic(() => import('./GroupTransferOwnerModal'));
const GroupInfoModal = dynamic(() => import('./GroupInfoModal'));
const ManageGroupMemberModal = dynamic(() => import('./GroupManageMember'));
function MemberTable({ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
onEditGroup,
onManageMember
}: {
onEditGroup: (groupId: string) => void;
onManageMember: (groupId: string) => void;
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
const [editGroupId, setEditGroupId] = useState<string>(); const [editGroupId, setEditGroupId] = useState<string>();
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({ const { ConfirmModal: ConfirmDeleteGroupModal, openConfirm: openDeleteGroupModal } = useConfirm({
type: 'delete', type: 'delete',
@@ -73,145 +90,184 @@ function MemberTable({
onOpen: onOpenChangeOwner, onOpen: onOpenChangeOwner,
onClose: onCloseChangeOwner onClose: onCloseChangeOwner
} = useDisclosure(); } = useDisclosure();
const onChangeOwner = (groupId: string) => { const onChangeOwner = (groupId: string) => {
setEditGroupId(groupId); setEditGroupId(groupId);
onOpenChangeOwner(); onOpenChangeOwner();
}; };
return ( return (
<MyBox> <>
<TableContainer overflow={'unset'} fontSize={'sm'}> <Flex justify={'space-between'} align={'center'} pb={'1rem'}>
<Table overflow={'unset'}> {Tabs}
<Thead> {userInfo?.team.permission.hasManagePer && (
<Tr bg={'white !important'}> <Button
<Th bg="myGray.100" borderLeftRadius="6px"> variant={'primary'}
{t('account_team:group_name')} size="md"
</Th> borderRadius={'md'}
<Th bg="myGray.100">{t('account_team:owner')}</Th> ml={3}
<Th bg="myGray.100">{t('account_team:member')}</Th> leftIcon={<MyIcon name="support/permission/collaborator" w={'14px'} />}
<Th bg="myGray.100" borderRightRadius="6px"> onClick={onOpenGroupInfo}
{t('common:common.Action')} >
</Th> {t('user:team.group.create')}
</Tr> </Button>
</Thead> )}
<Tbody> </Flex>
{groups?.map((group) => (
<Tr key={group._id} overflow={'unset'}> <MyBox flex={'1 0 0'} overflow={'auto'}>
<Td> <TableContainer overflow={'unset'} fontSize={'sm'}>
<HStack> <Table overflow={'unset'}>
<Thead>
<Tr bg={'white !important'}>
<Th bg="myGray.100" borderLeftRadius="6px">
{t('account_team:group_name')}
</Th>
<Th bg="myGray.100">{t('account_team:owner')}</Th>
<Th bg="myGray.100">{t('account_team:member')}</Th>
<Th bg="myGray.100" borderRightRadius="6px">
{t('common:common.Action')}
</Th>
</Tr>
</Thead>
<Tbody>
{groups?.map((group) => (
<Tr key={group._id} overflow={'unset'}>
<Td>
<HStack>
<MemberTag
name={
group.name === DefaultGroupName
? userInfo?.team.teamName ?? ''
: group.name
}
avatar={group.avatar}
/>
<Box>
({group.name === DefaultGroupName ? members.length : group.members.length})
</Box>
</HStack>
</Td>
<Td>
<MemberTag <MemberTag
name={ name={
group.name === DefaultGroupName ? userInfo?.team.teamName ?? '' : group.name group.name === DefaultGroupName
? members.find((item) => 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}
/> />
<Box> </Td>
({group.name === DefaultGroupName ? members.length : group.members.length}) <Td>
</Box> {group.name === DefaultGroupName ? (
</HStack> <AvatarGroup avatars={members.map((v) => v.avatar)} groupId={group._id} />
</Td> ) : hasGroupManagePer(group) ? (
<Td> <MyTooltip label={t('account_team:manage_member')}>
<MemberTag <Box cursor="pointer" onClick={() => onManageMember(group._id)}>
name={ <AvatarGroup
group.name === DefaultGroupName avatars={group.members.map(
? members.find((item) => item.role === 'owner')?.memberName ?? '' (v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? ''
: members.find( )}
(item) => groupId={group._id}
item.tmbId === />
group.members.find((item) => item.role === 'owner')?.tmbId </Box>
)?.memberName ?? '' </MyTooltip>
} ) : (
avatar={ <AvatarGroup
group.name === DefaultGroupName avatars={group.members.map(
? members.find((item) => item.role === 'owner')?.avatar ?? '' (v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? ''
: members.find( )}
(i) => groupId={group._id}
i.tmbId === group.members.find((item) => item.role === 'owner')?.tmbId />
)?.avatar ?? '' )}
} </Td>
/> <Td>
</Td> {hasGroupManagePer(group) && group.name !== DefaultGroupName && (
<Td> <MyMenu
{group.name === DefaultGroupName ? ( Button={<IconButton name={'more'} />}
<AvatarGroup avatars={members.map((v) => v.avatar)} groupId={group._id} /> menuList={[
) : hasGroupManagePer(group) ? ( {
<MyTooltip label={t('account_team:manage_member')}> children: [
<Box cursor="pointer" onClick={() => onManageMember(group._id)}> {
<AvatarGroup label: t('account_team:edit_info'),
avatars={group.members.map( icon: 'edit',
(v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? '' onClick: () => {
)} onEditGroup(group._id);
groupId={group._id} }
/> },
</Box> {
</MyTooltip> label: t('account_team:manage_member'),
) : ( icon: 'support/team/group',
<AvatarGroup onClick: () => {
avatars={group.members.map( onManageMember(group._id);
(v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? '' }
)} },
groupId={group._id} ...(isGroupOwner(group)
/> ? [
)} {
</Td> label: t('account_team:transfer_ownership'),
<Td> icon: 'modal/changePer',
{hasGroupManagePer(group) && group.name !== DefaultGroupName && ( onClick: () => {
<MyMenu onChangeOwner(group._id);
Button={<IconButton name={'more'} />} },
menuList={[ type: 'primary' as MenuItemType
{
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',
label: t('common:common.Delete'), onClick: () => {
icon: 'delete', openDeleteGroupModal(() => delDeleteGroup(group._id))();
onClick: () => { },
openDeleteGroupModal(() => delDeleteGroup(group._id))(); type: 'danger' as MenuItemType
}, }
type: 'danger' as MenuItemType ]
} : [])
] ]
: []) }
] ]}
} />
]} )}
/> </Td>
)} </Tr>
</Td> ))}
</Tr> </Tbody>
))} </Table>
</Tbody> </TableContainer>
</Table> </MyBox>
</TableContainer>
<ConfirmDeleteGroupModal /> <ConfirmDeleteGroupModal />
{isOpenChangeOwner && editGroupId && ( {isOpenChangeOwner && editGroupId && (
<ChangeOwnerModal groupId={editGroupId} onClose={onCloseChangeOwner} /> <ChangeOwnerModal groupId={editGroupId} onClose={onCloseChangeOwner} />
)} )}
</MyBox> {isOpenGroupInfo && (
<GroupInfoModal
onClose={() => {
onCloseGroupInfo();
setEditGroupId(undefined);
}}
editGroupId={editGroupId}
/>
)}
{isOpenManageGroupMember && (
<ManageGroupMemberModal
onClose={() => {
onCloseManageGroupMember();
setEditGroupId(undefined);
}}
editGroupId={editGroupId}
/>
)}
</>
); );
} }

View File

@@ -1,106 +1,224 @@
import Avatar from '@fastgpt/web/components/common/Avatar'; 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 { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { delRemoveMember } from '@/web/support/user/team/api'; import { delRemoveMember } from '@/web/support/user/team/api';
import Tag from '@fastgpt/web/components/common/Tag'; import Tag from '@fastgpt/web/components/common/Tag';
import Icon from '@fastgpt/web/components/common/Icon'; import Icon from '@fastgpt/web/components/common/Icon';
import GroupTags from '@/components/support/permission/Group/GroupTags'; import GroupTags from '@/components/support/permission/Group/GroupTags';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { TeamContext } from './context'; 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 InviteModal = dynamic(() => import('./InviteModal'));
const { userInfo } = useUserStore(); const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
const { t } = useTranslation(); 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({ const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm({
type: 'delete' type: 'delete'
}); });
const { members, groups, refetchMembers, refetchGroups } = useContextSelector( const { runAsync: onLeaveTeam } = useRequest2(
TeamContext, async () => {
(v) => v 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 ( return (
<MyBox> <>
<TableContainer overflow={'unset'} fontSize={'sm'}> <Flex justify={'space-between'} align={'center'} pb={'1rem'}>
<Table overflow={'unset'}> {Tabs}
<Thead> <HStack>
<Tr bgColor={'white !important'}> {userInfo?.team.permission.hasManagePer && feConfigs?.show_team_chat && (
<Th borderLeftRadius="6px" bgColor="myGray.100"> <Button
{t('account_team:user_name')} variant={'whitePrimary'}
</Th> size="md"
<Th bgColor="myGray.100">{t('account_team:member_group')}</Th> borderRadius={'md'}
<Th borderRightRadius="6px" bgColor="myGray.100"> ml={3}
{t('common:common.Action')} leftIcon={<MyIcon name="core/dataset/tag" w={'16px'} />}
</Th> onClick={() => {
</Tr> onOpenTeamTagsAsync();
</Thead> }}
<Tbody> >
{members?.map((item) => ( {t('account_team:label_sync')}
<Tr key={item.userId} overflow={'unset'}> </Button>
<Td> )}
<HStack> {userInfo?.team.permission.hasManagePer && (
<Avatar src={item.avatar} w={['18px', '22px']} borderRadius={'50%'} /> <Button
<Box className={'textEllipsis'}> variant={'primary'}
{item.memberName} size="md"
{item.status === 'waiting' && ( borderRadius={'md'}
<Tag ml="2" colorSchema="yellow"> ml={3}
{t('account_team:waiting')} leftIcon={<MyIcon name="common/inviteLight" w={'16px'} color={'white'} />}
</Tag> onClick={() => {
)} if (
</Box> teamPlanStatus?.standardConstants?.maxTeamMember &&
</HStack> teamPlanStatus.standardConstants.maxTeamMember <= members.length
</Td> ) {
<Td maxW={'300px'}> toast({
<GroupTags status: 'warning',
names={groups title: t('common:user.team.Over Max Member Tip', {
?.filter((group) => group.members.map((m) => m.tmbId).includes(item.tmbId)) max: teamPlanStatus.standardConstants.maxTeamMember
.map((g) => g.name)} })
max={3} });
/> setNotSufficientModalType(TeamErrEnum.teamMemberOverSize);
</Td> } else {
<Td> onOpenInvite();
{userInfo?.team.permission.hasManagePer && }
item.role !== TeamMemberRoleEnum.owner && }}
item.tmbId !== userInfo?.team.tmbId && ( >
<Icon {t('account_team:user_team_invite_member')}
name={'common/trash'} </Button>
cursor={'pointer'} )}
w="1rem" {!userInfo?.team.permission.isOwner && (
p="1" <Button
borderRadius="sm" variant={'whitePrimary'}
_hover={{ size="md"
color: 'red.600', borderRadius={'md'}
bgColor: 'myGray.100' ml={3}
}} leftIcon={<MyIcon name={'support/account/loginoutLight'} w={'14px'} />}
onClick={() => { onClick={() => openLeaveConfirm(onLeaveTeam)()}
openRemoveMember( >
() => {t('account_team:user_team_leave_team')}
delRemoveMember(item.tmbId).then(() => </Button>
Promise.all([refetchGroups(), refetchMembers()]) )}
), </HStack>
undefined, </Flex>
t('account_team:remove_tip', {
username: item.memberName
})
)();
}}
/>
)}
</Td>
</Tr>
))}
</Tbody>
</Table>
<ConfirmRemoveMemberModal /> <Box flex={'1 0 0'} overflow={'auto'}>
</TableContainer> <TableContainer overflow={'unset'} fontSize={'sm'}>
</MyBox> <Table overflow={'unset'}>
<Thead>
<Tr bgColor={'white !important'}>
<Th borderLeftRadius="6px" bgColor="myGray.100">
{t('account_team:user_name')}
</Th>
<Th bgColor="myGray.100">{t('account_team:member_group')}</Th>
<Th borderRightRadius="6px" bgColor="myGray.100">
{t('common:common.Action')}
</Th>
</Tr>
</Thead>
<Tbody>
{members?.map((item) => (
<Tr key={item.userId} overflow={'unset'}>
<Td>
<HStack>
<Avatar src={item.avatar} w={['18px', '22px']} borderRadius={'50%'} />
<Box className={'textEllipsis'}>
{item.memberName}
{item.status === 'waiting' && (
<Tag ml="2" colorSchema="yellow">
{t('account_team:waiting')}
</Tag>
)}
</Box>
</HStack>
</Td>
<Td maxW={'300px'}>
<GroupTags
names={groups
?.filter((group) => group.members.map((m) => m.tmbId).includes(item.tmbId))
.map((g) => g.name)}
max={3}
/>
</Td>
<Td>
{userInfo?.team.permission.hasManagePer &&
item.role !== TeamMemberRoleEnum.owner &&
item.tmbId !== userInfo?.team.tmbId && (
<Icon
name={'common/trash'}
cursor={'pointer'}
w="1rem"
p="1"
borderRadius="sm"
_hover={{
color: 'red.600',
bgColor: 'myGray.100'
}}
onClick={() => {
openRemoveMember(
() =>
delRemoveMember(item.tmbId).then(() =>
Promise.all([refetchGroups(), refetchMembers()])
),
undefined,
t('account_team:remove_tip', {
username: item.memberName
})
)();
}}
/>
)}
</Td>
</Tr>
))}
</Tbody>
</Table>
<ConfirmRemoveMemberModal />
</TableContainer>
</Box>
<ConfirmLeaveTeamModal />
{isOpenInvite && userInfo?.team?.teamId && (
<InviteModal
teamId={userInfo.team.teamId}
onClose={onCloseInvite}
onSuccess={refetchMembers}
/>
)}
{isOpenTeamTagsAsync && <TeamTagModal onClose={onCloseTeamTagsAsync} />}
</>
); );
} }

View File

@@ -71,7 +71,7 @@ function ActionButton({
); );
} }
function OrgTable() { function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { userInfo, isTeamAdmin } = useUserStore(); const { userInfo, isTeamAdmin } = useUserStore();
@@ -157,99 +157,69 @@ function OrgTable() {
}); });
return ( return (
<MyBox isLoading={isLoadingOrgs}> <>
<Box mb={3}> <Flex justify={'space-between'} align={'center'} pb={'1rem'}>
<Path paths={paths} rootName={userInfo?.team?.teamName} onClick={setParentPath} /> {Tabs}
</Box> </Flex>
<Flex w={'100%'} gap={'4'}> <MyBox flex={'1 0 0'} overflow={'auto'} isLoading={isLoadingOrgs}>
{/* Table */} <Box mb={3}>
<TableContainer overflow={'unset'} fontSize={'sm'} flexGrow={1}> <Path paths={paths} rootName={userInfo?.team?.teamName} onClick={setParentPath} />
<Table overflow={'unset'}> </Box>
<Thead> <Flex w={'100%'} gap={'4'}>
<Tr bg={'white !important'}> {/* Table */}
<Th bg="myGray.100" borderLeftRadius="6px"> <TableContainer overflow={'unset'} fontSize={'sm'} flexGrow={1}>
{t('common:Name')} <Table overflow={'unset'}>
</Th> <Thead>
<Th bg="myGray.100" borderRightRadius="6px"> <Tr bg={'white !important'}>
{t('common:common.Action')} <Th bg="myGray.100" borderLeftRadius="6px">
</Th> {t('common:Name')}
</Tr> </Th>
</Thead> <Th bg="myGray.100" borderRightRadius="6px">
<Tbody> {t('common:common.Action')}
{currentOrgs.map((org) => ( </Th>
<Tr key={org._id} overflow={'unset'}>
<Td>
<HStack
cursor={'pointer'}
onClick={() => setParentPath(getOrgChildrenPath(org))}
>
<MemberTag name={org.name} avatar={org.avatar} />
<Tag size="sm">{org.count}</Tag>
<MyIcon
name="core/chat/chevronRight"
w={'1rem'}
h={'1rem'}
color={'myGray.500'}
/>
</HStack>
</Td>
<Td w={'6rem'}>
{isTeamAdmin && (
<MyMenu
trigger="hover"
Button={<IconButton name="more" />}
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)
}
]
}
]}
/>
)}
</Td>
</Tr> </Tr>
))} </Thead>
{currentOrg?.members.map((member) => { <Tbody>
const memberInfo = members.find((m) => m.tmbId === member.tmbId); {currentOrgs.map((org) => (
if (!memberInfo) return null; <Tr key={org._id} overflow={'unset'}>
return (
<Tr key={member.tmbId}>
<Td> <Td>
<MemberTag name={memberInfo.memberName} avatar={memberInfo.avatar} /> <HStack
cursor={'pointer'}
onClick={() => setParentPath(getOrgChildrenPath(org))}
>
<MemberTag name={org.name} avatar={org.avatar} />
<Tag size="sm">{org.count}</Tag>
<MyIcon
name="core/chat/chevronRight"
w={'1rem'}
h={'1rem'}
color={'myGray.500'}
/>
</HStack>
</Td> </Td>
<Td w={'6rem'}> <Td w={'6rem'}>
{isTeamAdmin && ( {isTeamAdmin && (
<MyMenu <MyMenu
trigger={'hover'} trigger="hover"
Button={<IconButton name="more" />} Button={<IconButton name="more" />}
menuList={[ menuList={[
{ {
children: [ 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', icon: 'delete',
label: t('account_team:delete'), label: t('account_team:delete'),
type: 'danger', type: 'danger',
onClick: () => onClick: () => deleteOrgHandler(org._id)
openDeleteMemberModal(() =>
deleteMemberReq(currentOrg._id, member.tmbId)
)()
} }
] ]
} }
@@ -258,91 +228,126 @@ function OrgTable() {
)} )}
</Td> </Td>
</Tr> </Tr>
); ))}
})} {currentOrg?.members.map((member) => {
</Tbody> const memberInfo = members.find((m) => m.tmbId === member.tmbId);
</Table> if (!memberInfo) return null;
</TableContainer>
{/* Slider */}
<VStack w={'180px'} alignItems={'start'}>
<HStack gap={'6px'}>
<Avatar src={currentOrg?.avatar} w={'1rem'} h={'1rem'} rounded={'xs'} />
<Box fontWeight={500} color={'myGray.900'}>
{currentOrg?.name}
</Box>
{currentOrg?.path !== '' && (
<IconButton name="edit" onClick={() => setEditOrg(currentOrg)} />
)}
</HStack>
<Box fontSize={'xs'}>{currentOrg?.description || t('common:common.no_intro')}</Box>
<Divider my={'20px'} /> return (
<Tr key={member.tmbId}>
<Box fontWeight={500} fontSize="sm" color="myGray.900"> <Td>
{t('common:common.Action')} <MemberTag name={memberInfo.memberName} avatar={memberInfo.avatar} />
</Box> </Td>
{currentOrg && isTeamAdmin && ( <Td w={'6rem'}>
<VStack gap="13px" w="100%"> {isTeamAdmin && (
<ActionButton <MyMenu
icon="common/add2" trigger={'hover'}
text={t('account_team:create_sub_org')} Button={<IconButton name="more" />}
onClick={() => { menuList={[
setEditOrg({ {
...defaultOrgForm, children: [
parentId: currentOrg?._id {
}); icon: 'delete',
}} label: t('account_team:delete'),
/> type: 'danger',
<ActionButton onClick: () =>
icon="common/administrator" openDeleteMemberModal(() =>
text={t('account_team:manage_member')} deleteMemberReq(currentOrg._id, member.tmbId)
onClick={() => setManageMemberOrg(currentOrg)} )()
/> }
]
}
]}
/>
)}
</Td>
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
{/* Slider */}
<VStack w={'180px'} alignItems={'start'}>
<HStack gap={'6px'}>
<Avatar src={currentOrg?.avatar} w={'1rem'} h={'1rem'} rounded={'xs'} />
<Box fontWeight={500} color={'myGray.900'}>
{currentOrg?.name}
</Box>
{currentOrg?.path !== '' && ( {currentOrg?.path !== '' && (
<> <IconButton name="edit" onClick={() => setEditOrg(currentOrg)} />
<ActionButton
icon="common/file/move"
text={t('account_team:move_org')}
onClick={() => setMovingOrg(currentOrg)}
/>
<ActionButton
icon="delete"
text={t('account_team:delete_org')}
onClick={() => deleteOrgHandler(currentOrg._id)}
/>
</>
)} )}
</VStack> </HStack>
)} <Box fontSize={'xs'}>{currentOrg?.description || t('common:common.no_intro')}</Box>
</VStack>
</Flex>
{!!editOrg && ( <Divider my={'20px'} />
<OrgInfoModal
editOrg={editOrg}
onClose={() => setEditOrg(undefined)}
onSuccess={refetchOrgs}
/>
)}
{!!movingOrg && (
<OrgMoveModal
orgs={orgs}
movingOrg={movingOrg}
onClose={() => setMovingOrg(undefined)}
onSuccess={refetchOrgs}
/>
)}
{!!manageMemberOrg && (
<OrgMemberManageModal
currentOrg={manageMemberOrg}
refetchOrgs={refetchOrgs}
onClose={() => setManageMemberOrg(undefined)}
/>
)}
<ConfirmDeleteOrgModal /> <Box fontWeight={500} fontSize="sm" color="myGray.900">
<ConfirmDeleteMember /> {t('common:common.Action')}
</MyBox> </Box>
{currentOrg && isTeamAdmin && (
<VStack gap="13px" w="100%">
<ActionButton
icon="common/add2"
text={t('account_team:create_sub_org')}
onClick={() => {
setEditOrg({
...defaultOrgForm,
parentId: currentOrg?._id
});
}}
/>
<ActionButton
icon="common/administrator"
text={t('account_team:manage_member')}
onClick={() => setManageMemberOrg(currentOrg)}
/>
{currentOrg?.path !== '' && (
<>
<ActionButton
icon="common/file/move"
text={t('account_team:move_org')}
onClick={() => setMovingOrg(currentOrg)}
/>
<ActionButton
icon="delete"
text={t('account_team:delete_org')}
onClick={() => deleteOrgHandler(currentOrg._id)}
/>
</>
)}
</VStack>
)}
</VStack>
</Flex>
{!!editOrg && (
<OrgInfoModal
editOrg={editOrg}
onClose={() => setEditOrg(undefined)}
onSuccess={refetchOrgs}
/>
)}
{!!movingOrg && (
<OrgMoveModal
orgs={orgs}
movingOrg={movingOrg}
onClose={() => setMovingOrg(undefined)}
onSuccess={refetchOrgs}
/>
)}
{!!manageMemberOrg && (
<OrgMemberManageModal
currentOrg={manageMemberOrg}
refetchOrgs={refetchOrgs}
onClose={() => setManageMemberOrg(undefined)}
/>
)}
<ConfirmDeleteOrgModal />
<ConfirmDeleteMember />
</MyBox>
</>
); );
} }

View File

@@ -1,4 +1,4 @@
import React from 'react'; import React, { useMemo, useState } from 'react';
import { import {
Box, Box,
Checkbox, Checkbox,
@@ -10,7 +10,9 @@ import {
Th, Th,
Thead, Thead,
Text, Text,
Tr Tr,
Flex,
Button
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; 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 { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
import { import {
TeamManagePermissionVal, TeamManagePermissionVal,
TeamPermissionList,
TeamWritePermissionVal TeamWritePermissionVal
} from '@fastgpt/global/support/permission/user/constant'; } from '@fastgpt/global/support/permission/user/constant';
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller'; 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 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({ function PermissionManage({
isOpenAddPermission, Tabs,
onCloseAddPermission onOpenAddMember
}: { }: {
isOpenAddPermission: boolean; Tabs: React.ReactNode;
onCloseAddPermission: () => void; onOpenAddMember: () => void;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
const [searchKey, setSearchKey] = useState('');
const { runAsync: refetchClbs, data: clbs = { tmb: [], group: [], org: [] } } = useRequest2( const collaboratorList = useContextSelector(
getTeamClbs, CollaboratorContext,
{ (state) => state.collaboratorList
manual: false, );
refreshDeps: [userInfo?.team?.teamId] const onUpdateCollaborators = useContextSelector(
} CollaboratorContext,
(state) => state.onUpdateCollaborators
);
const onDelOneCollaborator = useContextSelector(
CollaboratorContext,
(state) => state.onDelOneCollaborator
); );
const [isExpandMember, setExpandMember] = useToggle(true); const [isExpandMember, setExpandMember] = useToggle(true);
const [isExpandGroup, setExpandGroup] = useToggle(true); const [isExpandGroup, setExpandGroup] = useToggle(true);
const [isExpandOrg, setExpandOrg] = useToggle(true); const [isExpandOrg, setExpandOrg] = useToggle(true);
const members = useCreation( const { tmbList, groupList, orgList } = useMemo(() => {
() => const tmbList: CollaboratorItemType[] = [];
clbs.tmb.map((item) => ({ const groupList: CollaboratorItemType[] = [];
...item, const orgList: CollaboratorItemType[] = [];
permission: new TeamPermission({ per: item.permission })
})),
[clbs]
);
const groups = useCreation( collaboratorList.forEach((item) => {
() => if (item.tmbId) {
clbs.group.map((item) => ({ tmbList.push(item);
...item, } else if (item.groupId) {
permission: new TeamPermission({ per: item.permission }) groupList.push(item);
})), } else if (item.orgId) {
[clbs] orgList.push(item);
);
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
});
}
}
} }
if (orgId) { });
const org = orgs.find((org) => String(org.orgId) === orgId);
if (org) { return {
const permission = new TeamPermission({ per: org.permission.value }); tmbList,
switch (per) { groupList,
case 'write': orgList
permission.addPer(TeamWritePermissionVal); };
return onUpdateMemberPermission({ }, [collaboratorList]);
orgId: org.orgId,
permission: permission.value const { runAsync: onUpdatePermission, loading: addLoading } = useRequest2(
}); async ({ id, type, per }: { id: string; type: 'add' | 'remove'; per: 'write' | 'manage' }) => {
case 'manage': const clb = collaboratorList.find(
permission.addPer(TeamManagePermissionVal); (clb) => clb.tmbId === id || clb.groupId === id || clb.orgId === id
return onUpdateMemberPermission({ );
orgId: org.orgId,
permission: permission.value if (!clb) return;
});
} const updatePer = per === 'write' ? TeamWritePermissionVal : TeamManagePermissionVal;
} const permission = new TeamPermission({ per: clb.permission.value });
} if (type === 'add') {
if (memberId) { permission.addPer(updatePer);
const member = members?.find((member) => member.tmbId === memberId); } else {
if (member) { permission.removePer(updatePer);
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 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( const { runAsync: onDeleteMemberPermission, loading: deleteLoading } =
async ({ useRequest2(onDelOneCollaborator);
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 userManage = userInfo?.permission.hasManagePer; const userManage = userInfo?.permission.hasManagePer;
return ( return (
<TableContainer fontSize={'sm'}> <>
<Table> <Flex justify={'space-between'} align={'center'} pb={'1rem'}>
<Thead> {Tabs}
<Tr bg={'white !important'}> <Box ml="auto">
<Th bg="myGray.100" borderLeftRadius="md" maxW={'150px'}> {/* <SearchInput
{`${t('user:team.group.members')} / ${t('user:team.org.org')} / ${t('user:team.group.group')}`} placeholder={t('user:team.group.search_placeholder')}
<QuestionTip ml="1" label={t('user:team.group.permission_tip')} /> w="200px"
</Th> value={searchKey}
<Th bg="myGray.100"> onChange={(e) => setSearchKey(e.target.value)}
<Box mx="auto" w="fit-content"> /> */}
{t('user:team.group.permission.write')} </Box>
</Box> {userInfo?.team.permission.hasManagePer && (
</Th> <Button
<Th bg="myGray.100"> variant={'primary'}
<Box mx="auto" w="fit-content"> size="md"
{t('user:team.group.permission.manage')} borderRadius={'md'}
<QuestionTip ml="1" label={t('user:team.group.manage_tip')} /> ml={3}
</Box> leftIcon={<MyIcon name="common/add2" w={'14px'} />}
</Th> onClick={onOpenAddMember}
<Th bg="myGray.100" borderRightRadius="md"> >
<Box mx="auto" w="fit-content"> {t('common:common.Add')}
{t('common:common.Action')} </Button>
</Box> )}
</Th> </Flex>
</Tr> <MyBox isLoading={addLoading || deleteLoading}>
</Thead> <TableContainer fontSize={'sm'}>
<Tbody> <Table>
<Tr overflow={'unset'} border="none"> <Thead>
<HStack paddingX={'8px'} paddingY={'4px'}> <Tr bg={'white !important'}>
<MyIconButton <Th bg="myGray.100" borderLeftRadius="md" maxW={'150px'}>
icon={isExpandMember ? 'common/downArrowFill' : 'common/rightArrowFill'} {`${t('user:team.group.members')} / ${t('user:team.org.org')} / ${t('user:team.group.group')}`}
onClick={setExpandMember.toggle} <QuestionTip ml="1" label={t('user:team.group.permission_tip')} />
/> </Th>
<Text>{t('user:team.group.members')}</Text> <Th bg="myGray.100">
</HStack> <Box mx="auto" w="fit-content">
</Tr> {t('user:team.group.permission.write')}
{isExpandMember && </Box>
members.map((member) => ( </Th>
<Tr key={member.tmbId} overflow={'unset'} border="none"> <Th bg="myGray.100">
<Td border="none"> <Box mx="auto" w="fit-content">
<HStack> {t('user:team.group.permission.manage')}
<Avatar src={member.avatar} w="1.5rem" borderRadius={'50%'} /> <QuestionTip ml="1" label={t('user:team.group.manage_tip')} />
<Box>{member.name}</Box> </Box>
</Th>
<Th bg="myGray.100" borderRightRadius="md">
<Box mx="auto" w="fit-content">
{t('common:common.Action')}
</Box>
</Th>
</Tr>
</Thead>
<Tbody>
<>
<Tr userSelect={'none'}>
<HStack pl={3} pt={3} pb={isExpandMember && !!tmbList.length ? 0 : 3}>
<MyIconButton
icon={isExpandMember ? 'common/downArrowFill' : 'common/rightArrowFill'}
onClick={setExpandMember.toggle}
/>
<Box color={'myGray.900'}>{t('user:team.group.members')}</Box>
</HStack> </HStack>
</Td> </Tr>
<Td border="none"> {isExpandMember &&
<Box mx="auto" w="fit-content"> tmbList.map((member) => (
<Checkbox <Tr key={member.tmbId}>
isDisabled={member.permission.isOwner || !userManage} <Td pl={10}>
isChecked={member.permission.hasWritePer} <HStack>
onChange={(e) => <Avatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
e.target.checked <Box>{member.name}</Box>
? onAddPermission({ memberId: String(member.tmbId), per: 'write' }) </HStack>
: onRemovePermission({ memberId: String(member.tmbId), per: 'write' }) </Td>
} <Td>
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={member.permission.isOwner || !userManage}
isChecked={member.permission.hasWritePer}
onChange={(e) =>
e.target.checked
? onUpdatePermission({
id: member.tmbId!,
type: 'add',
per: 'write'
})
: onUpdatePermission({
id: member.tmbId!,
type: 'remove',
per: 'write'
})
}
/>
</Box>
</Td>
<Td>
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={member.permission.isOwner || !userInfo?.permission.isOwner}
isChecked={member.permission.hasManagePer}
onChange={(e) =>
e.target.checked
? onUpdatePermission({
id: member.tmbId!,
type: 'add',
per: 'manage'
})
: onUpdatePermission({
id: member.tmbId!,
type: 'remove',
per: 'manage'
})
}
/>
</Box>
</Td>
{userManage &&
!member.permission.isOwner &&
userInfo?.team.tmbId !== member.tmbId && (
<Td>
<Box mx="auto" w="fit-content">
<MyIconButton
icon="common/trash"
onClick={() =>
onDeleteMemberPermission({ tmbId: String(member.tmbId) })
}
/>
</Box>
</Td>
)}
</Tr>
))}
</>
<>
<Tr borderBottom={'1px solid'} borderColor={'myGray.200'} />
<Tr userSelect={'none'}>
<HStack pl={3} pt={3} pb={isExpandOrg && !!orgList.length ? 0 : 3}>
<MyIconButton
icon={isExpandOrg ? 'common/downArrowFill' : 'common/rightArrowFill'}
onClick={setExpandOrg.toggle}
/> />
</Box> <Text>{t('user:team.org.org')}</Text>
</Td> </HStack>
<Td border="none"> </Tr>
<Box mx="auto" w="fit-content"> {isExpandOrg &&
<Checkbox orgList.map((org) => (
isDisabled={member.permission.isOwner || !userInfo?.permission.isOwner} <Tr key={org.orgId}>
isChecked={member.permission.hasManagePer} <Td pl={10}>
onChange={(e) => <MemberTag name={org.name} avatar={org.avatar} />
e.target.checked </Td>
? onAddPermission({ memberId: String(member.tmbId), per: 'manage' }) <Td>
: onRemovePermission({ memberId: String(member.tmbId), per: 'manage' }) <Box mx="auto" w="fit-content">
} <Checkbox
isDisabled={!userManage}
isChecked={org.permission.hasWritePer}
onChange={(e) =>
e.target.checked
? onUpdatePermission({ id: org.orgId!, type: 'add', per: 'write' })
: onUpdatePermission({
id: org.orgId!,
type: 'remove',
per: 'write'
})
}
/>
</Box>
</Td>
<Td>
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userInfo?.permission.isOwner}
isChecked={org.permission.hasManagePer}
onChange={(e) =>
e.target.checked
? onUpdatePermission({ id: org.orgId!, type: 'add', per: 'manage' })
: onUpdatePermission({
id: org.orgId!,
type: 'remove',
per: 'manage'
})
}
/>
</Box>
</Td>
{userInfo?.permission.isOwner && (
<Td>
<Box mx="auto" w="fit-content">
<MyIconButton
icon="common/trash"
onClick={() => onDeleteMemberPermission({ orgId: org.orgId! })}
/>
</Box>
</Td>
)}
</Tr>
))}
</>
<>
<Tr borderBottom={'1px solid'} borderColor={'myGray.200'} />
<Tr userSelect={'none'}>
<HStack pl={3} pt={3} pb={isExpandGroup && !!groupList.length ? 0 : 3}>
<MyIconButton
icon={isExpandGroup ? 'common/downArrowFill' : 'common/rightArrowFill'}
onClick={setExpandGroup.toggle}
/> />
</Box> <Text>{t('user:team.group.group')}</Text>
</Td> </HStack>
{userManage && </Tr>
!member.permission.isOwner && {isExpandGroup &&
userInfo?.team.tmbId !== member.tmbId && ( groupList.map((group) => (
<Td border="none"> <Tr key={group.groupId}>
<Box mx="auto" w="fit-content"> <Td pl={10}>
<MyIconButton <MemberTag
icon="common/trash" name={
onClick={() => onDeleteMemberPermission({ tmbId: String(member.tmbId) })} group.name === DefaultGroupName
? userInfo?.team.teamName ?? ''
: group.name
}
avatar={group.avatar}
/> />
</Box> </Td>
</Td> <Td>
)} <Box mx="auto" w="fit-content">
</Tr> <Checkbox
))} isDisabled={!userManage}
isChecked={group.permission.hasWritePer}
<Tr borderBottom={'1px solid'} borderColor={'myGray.200'} /> onChange={(e) =>
<Tr overflow={'unset'} border="none"> e.target.checked
<HStack paddingX={'8px'} paddingY={'4px'}> ? onUpdatePermission({
<MyIconButton id: group.groupId!,
icon={isExpandOrg ? 'common/downArrowFill' : 'common/rightArrowFill'} type: 'add',
onClick={setExpandOrg.toggle} per: 'write'
/> })
<Text>{t('user:team.org.org')}</Text> : onUpdatePermission({
</HStack> id: group.groupId!,
</Tr> type: 'remove',
per: 'write'
{isExpandOrg && })
orgs.map((org) => ( }
<Tr key={org.orgId} overflow={'unset'} border="none"> />
<Td border="none"> </Box>
<MemberTag name={org.name} avatar={org.avatar} /> </Td>
</Td> <Td>
<Td border="none"> <Box mx="auto" w="fit-content">
<Box mx="auto" w="fit-content"> <Checkbox
<Checkbox isDisabled={!userInfo?.permission.isOwner}
isDisabled={!userManage} isChecked={group.permission.hasManagePer}
isChecked={org.permission.hasWritePer} onChange={(e) =>
onChange={(e) => e.target.checked
e.target.checked ? onUpdatePermission({
? onAddPermission({ orgId: org.orgId, per: 'write' }) id: group.groupId!,
: onRemovePermission({ orgId: org.orgId, per: 'write' }) type: 'add',
} per: 'manage'
/> })
</Box> : onUpdatePermission({
</Td> id: group.groupId!,
<Td border="none"> type: 'remove',
<Box mx="auto" w="fit-content"> per: 'manage'
<Checkbox })
isDisabled={!userInfo?.permission.isOwner} }
isChecked={org.permission.hasManagePer} />
onChange={(e) => </Box>
e.target.checked </Td>
? onAddPermission({ orgId: org.orgId, per: 'manage' }) {userInfo?.permission.isOwner && (
: onRemovePermission({ orgId: org.orgId, per: 'manage' }) <Td>
} <Box mx="auto" w="fit-content">
/> <MyIconButton
</Box> icon="common/trash"
</Td> onClick={() => onDeleteMemberPermission({ groupId: group.groupId! })}
{userInfo?.permission.isOwner && ( />
<Td border="none"> </Box>
<Box mx="auto" w="fit-content"> </Td>
<MyIconButton )}
icon="common/trash" </Tr>
onClick={() => onDeleteMemberPermission({ orgId: org.orgId })} ))}
/> </>
</Box> </Tbody>
</Td> </Table>
)} </TableContainer>
</Tr> </MyBox>
))} </>
<Tr borderBottom={'1px solid'} borderColor={'myGray.200'} />
<Tr overflow={'unset'} border="none">
<HStack paddingX={'8px'} paddingY={'4px'}>
<MyIconButton
icon={isExpandGroup ? 'common/downArrowFill' : 'common/rightArrowFill'}
onClick={setExpandGroup.toggle}
/>
<Text>{t('user:team.group.group')}</Text>
</HStack>
</Tr>
{isExpandGroup &&
groups.map((group) => (
<Tr key={group.groupId} overflow={'unset'} border="none">
<Td border="none">
<MemberTag
name={
group.name === DefaultGroupName ? userInfo?.team.teamName ?? '' : group.name
}
avatar={group.avatar}
/>
</Td>
<Td border="none">
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userManage}
isChecked={group.permission.hasWritePer}
onChange={(e) =>
e.target.checked
? onAddPermission({ groupId: group.groupId, per: 'write' })
: onRemovePermission({ groupId: group.groupId, per: 'write' })
}
/>
</Box>
</Td>
<Td border="none">
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userInfo?.permission.isOwner}
isChecked={group.permission.hasManagePer}
onChange={(e) =>
e.target.checked
? onAddPermission({ groupId: group.groupId, per: 'manage' })
: onRemovePermission({ groupId: group.groupId, per: 'manage' })
}
/>
</Box>
</Td>
{userInfo?.permission.isOwner && (
<Td border="none">
<Box mx="auto" w="fit-content">
<MyIconButton
icon="common/trash"
onClick={() => onDeleteMemberPermission({ groupId: group.groupId })}
/>
</Box>
</Td>
)}
</Tr>
))}
</Tbody>
</Table>
{isOpenAddPermission && (
<MemberModal
onClose={() => {
refetchClbs();
onCloseAddPermission();
}}
mode="all"
/>
)}
</TableContainer>
); );
} }
export default PermissionManage; export const Render = ({ Tabs }: { Tabs: React.ReactNode }) => {
const { userInfo } = useUserStore();
return userInfo?.team ? (
<CollaboratorContextProvider
permission={userInfo?.team.permission}
permissionList={TeamPermissionList}
onGetCollaboratorList={getTeamClbs}
onUpdateCollaborators={updateMemberPermission}
onDelOneCollaborator={deleteMemberPermission}
refreshDeps={[userInfo?.team.teamId]}
>
{({ onOpenAddMember }) => <PermissionManage Tabs={Tabs} onOpenAddMember={onOpenAddMember} />}
</CollaboratorContextProvider>
) : null;
};
export default Render;

View File

@@ -25,8 +25,6 @@ type TeamModalContextType = {
refetchMembers: () => void; refetchMembers: () => void;
refetchTeams: () => void; refetchTeams: () => void;
refetchGroups: () => void; refetchGroups: () => void;
searchKey: string;
setSearchKey: React.Dispatch<React.SetStateAction<string>>;
teamSize: number; teamSize: number;
}; };
@@ -51,10 +49,6 @@ export const TeamContext = createContext<TeamModalContextType>({
throw new Error('Function not implemented.'); throw new Error('Function not implemented.');
}, },
searchKey: '',
setSearchKey: function (_value: React.SetStateAction<string>): void {
throw new Error('Function not implemented.');
},
teamSize: 0 teamSize: 0
}); });
@@ -62,7 +56,6 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
const { t } = useTranslation(); const { t } = useTranslation();
const [editTeamData, setEditTeamData] = useState<EditTeamFormDataType>(); const [editTeamData, setEditTeamData] = useState<EditTeamFormDataType>();
const { userInfo, initUserInfo, loadAndGetTeamMembers } = useUserStore(); const { userInfo, initUserInfo, loadAndGetTeamMembers } = useUserStore();
const [searchKey, setSearchKey] = useState('');
const { const {
data: myTeams = [], data: myTeams = [],
@@ -115,8 +108,6 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
refetchTeams, refetchTeams,
isLoading, isLoading,
onSwitchTeam, onSwitchTeam,
searchKey,
setSearchKey,
// create | update team // create | update team
setEditTeamData, setEditTeamData,

View File

@@ -1,33 +1,25 @@
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs'; import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
import AccountContainer from '../components/AccountContainer'; 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 Icon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import TeamSelector from '../components/TeamSelector'; import TeamSelector from '../components/TeamSelector';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import React, { useState } from 'react'; import React, { useMemo } from 'react';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs'; 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 MyIcon from '@fastgpt/web/components/common/Icon';
import { useToast } from '@fastgpt/web/hooks/useToast'; 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 { useSystemStore } from '@/web/common/system/useSystemStore';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { TeamContext, TeamModalContextProvider } from './components/context'; import { TeamContext, TeamModalContextProvider } from './components/context';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import TeamTagModal from '@/components/support/user/team/TeamTagModal';
import MemberTable from './components/MemberTable'; 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 PermissionManage = dynamic(() => import('./components/PermissionManage/index'));
const GroupManage = dynamic(() => import('./components/GroupManage/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')); const OrgManage = dynamic(() => import('./components/OrgManage/index'));
export enum TeamTabEnum { export enum TeamTabEnum {
@@ -39,79 +31,37 @@ export enum TeamTabEnum {
const Team = () => { const Team = () => {
const router = useRouter(); 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 { teamTab = TeamTabEnum.member } = router.query as { teamTab: `${TeamTabEnum}` };
const { const { t } = useTranslation();
isOpen: isOpenTeamTagsAsync, const { userInfo } = useUserStore();
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 { runAsync: onLeaveTeam } = useRequest2( const { setEditTeamData, teamSize, isLoading } = useContextSelector(TeamContext, (v) => v);
async () => {
const defaultTeam = myTeams.find((item) => item.defaultTeam) || myTeams[0]; const Tabs = useMemo(
// change to personal team () => (
// get members <FillRowTabs
onSwitchTeam(defaultTeam.teamId); list={[
return delLeaveTeam(); { label: t('account_team:member'), value: TeamTabEnum.member },
}, { label: t('account_team:org'), value: TeamTabEnum.org },
{ { label: t('account_team:group'), value: TeamTabEnum.group },
onSuccess() { { label: t('account_team:permission'), value: TeamTabEnum.permission }
refetchTeams(); ]}
}, px={'1rem'}
errorToast: t('account_team:user_team_leave_team_failed') value={teamTab}
} onChange={(e) => {
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<string>();
const onEditGroup = (groupId: string) => {
setEditGroupId(groupId);
onOpenGroupInfo();
};
const onManageMember = (groupId: string) => {
setEditGroupId(groupId);
onOpenManageGroupMember();
};
return ( return (
<AccountContainer isLoading={isLoading}> <AccountContainer isLoading={isLoading}>
{/* header */} {/* header */}
@@ -175,159 +125,11 @@ const Team = () => {
{/* table */} {/* table */}
<Box py={'1.5rem'} px={'2rem'}> <Box py={'1.5rem'} px={'2rem'}>
<Flex justify={'space-between'} align={'center'} pb={'1rem'}> {teamTab === TeamTabEnum.member && <MemberTable Tabs={Tabs} />}
<FillRowTabs {teamTab === TeamTabEnum.org && <OrgManage Tabs={Tabs} />}
list={[ {teamTab === TeamTabEnum.group && <GroupManage Tabs={Tabs} />}
{ label: t('account_team:member'), value: TeamTabEnum.member }, {teamTab === TeamTabEnum.permission && <PermissionManage Tabs={Tabs} />}
{ label: t('account_team:org'), value: TeamTabEnum.org },
{ label: t('account_team:group'), value: TeamTabEnum.group },
{ label: t('account_team:permission'), value: TeamTabEnum.permission }
]}
px={'1rem'}
value={teamTab}
onChange={(e) => {
router.replace({
query: {
...router.query,
teamTab: e
}
});
}}
/>
<Flex alignItems={'center'}>
{teamTab === TeamTabEnum.member &&
userInfo?.team.permission.hasManagePer &&
feConfigs?.show_team_chat && (
<Button
variant={'whitePrimary'}
size="md"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name="core/dataset/tag" w={'16px'} />}
onClick={() => {
onOpenTeamTagsAsync();
}}
>
{t('account_team:label_sync')}
</Button>
)}
{teamTab === TeamTabEnum.member && userInfo?.team.permission.hasManagePer && (
<Button
variant={'primary'}
size="md"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name="common/inviteLight" w={'16px'} color={'white'} />}
onClick={() => {
if (
teamPlanStatus?.standardConstants?.maxTeamMember &&
teamPlanStatus.standardConstants.maxTeamMember <= members.length
) {
toast({
status: 'warning',
title: t('common:user.team.Over Max Member Tip', {
max: teamPlanStatus.standardConstants.maxTeamMember
})
});
setNotSufficientModalType(TeamErrEnum.teamMemberOverSize);
} else {
onOpenInvite();
}
}}
>
{t('account_team:user_team_invite_member')}
</Button>
)}
{teamTab === TeamTabEnum.member && !userInfo?.team.permission.isOwner && (
<Button
variant={'whitePrimary'}
size="md"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name={'support/account/loginoutLight'} w={'14px'} />}
onClick={() => openLeaveConfirm(onLeaveTeam)()}
>
{t('account_team:user_team_leave_team')}
</Button>
)}
{teamTab === TeamTabEnum.group && userInfo?.team.permission.hasManagePer && (
<Button
variant={'primary'}
size="md"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name="support/permission/collaborator" w={'14px'} />}
onClick={onOpenGroupInfo}
>
{t('user:team.group.create')}
</Button>
)}
{teamTab === TeamTabEnum.permission && (
<Box ml="auto">
<SearchInput
placeholder={t('user:team.group.search_placeholder')}
w="200px"
value={searchKey}
onChange={(e) => setSearchKey(e.target.value)}
/>
</Box>
)}
{teamTab === TeamTabEnum.permission && userInfo?.team.permission.hasManagePer && (
<Button
variant={'primary'}
size="md"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name="common/add2" w={'14px'} />}
onClick={onOpenAddPermission}
>
{t('common:common.Add')}
</Button>
)}
</Flex>
</Flex>
<Box flex={'1 0 0'} overflow={'auto'}>
{teamTab === TeamTabEnum.member && <MemberTable />}
{teamTab === TeamTabEnum.group && (
<GroupManage onEditGroup={onEditGroup} onManageMember={onManageMember} />
)}
{teamTab === TeamTabEnum.org && <OrgManage />}
{teamTab === TeamTabEnum.permission && (
<PermissionManage
isOpenAddPermission={isOpenAddPermission}
onCloseAddPermission={onCloseAddPermission}
/>
)}
</Box>
</Box> </Box>
{isOpenInvite && userInfo?.team?.teamId && (
<InviteModal
teamId={userInfo.team.teamId}
onClose={onCloseInvite}
onSuccess={refetchMembers}
/>
)}
{isOpenTeamTagsAsync && <TeamTagModal onClose={onCloseTeamTagsAsync} />}
{isOpenGroupInfo && (
<GroupInfoModal
onClose={() => {
onCloseGroupInfo();
setEditGroupId(undefined);
}}
editGroupId={editGroupId}
/>
)}
{isOpenManageGroupMember && (
<ManageGroupMemberModal
onClose={() => {
onCloseManageGroupMember();
setEditGroupId(undefined);
}}
editGroupId={editGroupId}
/>
)}
<ConfirmLeaveTeamModal />
</AccountContainer> </AccountContainer>
); );
}; };

View File

@@ -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<writefileBody, writefileQuery>,
res: ApiResponseType<any>
): Promise<writefileResponse> {
await authCert({ req, authRoot: true });
const { name, content } = req.body;
await fs.promises.writeFile(`public/${name}`, content);
return {};
}
export default NextAPI(handler);

View File

@@ -187,7 +187,6 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
)} )}
<Box mt={6}> <Box mt={6}>
<CollaboratorContextProvider <CollaboratorContextProvider
mode="all"
permission={appDetail.permission} permission={appDetail.permission}
onGetCollaboratorList={() => getCollaboratorList(appDetail._id)} onGetCollaboratorList={() => getCollaboratorList(appDetail._id)}
permissionList={AppPermissionList} permissionList={AppPermissionList}

View File

@@ -57,7 +57,7 @@ const NodeCard = (props: Props) => {
name = t('common:core.module.template.UnKnow Module'), name = t('common:core.module.template.UnKnow Module'),
intro, intro,
minW = '300px', minW = '300px',
maxW = '600px', maxW = '666px',
minH = 0, minH = 0,
w = 'full', w = 'full',
h = 'full', h = 'full',

View File

@@ -431,7 +431,6 @@ const ListItem = () => {
avatar={editPerApp.avatar} avatar={editPerApp.avatar}
name={editPerApp.name} name={editPerApp.name}
managePer={{ managePer={{
mode: 'all',
permission: editPerApp.permission, permission: editPerApp.permission,
onGetCollaboratorList: () => getCollaboratorList(editPerApp._id), onGetCollaboratorList: () => getCollaboratorList(editPerApp._id),
permissionList: AppPermissionList, permissionList: AppPermissionList,

View File

@@ -301,7 +301,6 @@ const MyApps = () => {
deleteTip={t('app:confirm_delete_folder_tip')} deleteTip={t('app:confirm_delete_folder_tip')}
onDelete={() => onDeleFolder(folderDetail._id)} onDelete={() => onDeleFolder(folderDetail._id)}
managePer={{ managePer={{
mode: 'all',
permission: folderDetail.permission, permission: folderDetail.permission,
onGetCollaboratorList: () => getCollaboratorList(folderDetail._id), onGetCollaboratorList: () => getCollaboratorList(folderDetail._id),
permissionList: AppPermissionList, permissionList: AppPermissionList,

View File

@@ -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 React from 'react';
import CollaboratorContextProvider, { import CollaboratorContextProvider, {
MemberManagerInputPropsType MemberManagerInputPropsType

View File

@@ -354,7 +354,6 @@ const Info = ({ datasetId }: { datasetId: string }) => {
<Box> <Box>
<MemberManager <MemberManager
managePer={{ managePer={{
mode: 'all',
permission: datasetDetail.permission, permission: datasetDetail.permission,
onGetCollaboratorList: () => getCollaboratorList(datasetId), onGetCollaboratorList: () => getCollaboratorList(datasetId),
permissionList: DatasetPermissionList, permissionList: DatasetPermissionList,

View File

@@ -440,7 +440,6 @@ function List() {
avatar={editPerDataset.avatar} avatar={editPerDataset.avatar}
name={editPerDataset.name} name={editPerDataset.name}
managePer={{ managePer={{
mode: 'all',
permission: editPerDataset.permission, permission: editPerDataset.permission,
onGetCollaboratorList: () => getCollaboratorList(editPerDataset._id), onGetCollaboratorList: () => getCollaboratorList(editPerDataset._id),
permissionList: DatasetPermissionList, permissionList: DatasetPermissionList,

View File

@@ -238,7 +238,6 @@ const Dataset = () => {
}) })
} }
managePer={{ managePer={{
mode: 'all',
permission: folderDetail.permission, permission: folderDetail.permission,
onGetCollaboratorList: () => getCollaboratorList(folderDetail._id), onGetCollaboratorList: () => getCollaboratorList(folderDetail._id),
permissionList: DatasetPermissionList, permissionList: DatasetPermissionList,

View File

@@ -0,0 +1,5 @@
export type CreateModelParams = {
name: string;
description: string;
prompt: string;
};

View File

@@ -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 = <T>(url: string, data: any, method: Method): Promise<T> => {
/* 去空 */
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 = {};

View File

@@ -9,6 +9,7 @@ import { TOKEN_ERROR_CODE } from '@fastgpt/global/common/error/errorCode';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import { useSystemStore } from '../system/useSystemStore'; import { useSystemStore } from '../system/useSystemStore';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils'; import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
import { i18nT } from '@fastgpt/web/i18n/utils';
interface ConfigType { interface ConfigType {
headers?: { [key: string]: string }; headers?: { [key: string]: string };
@@ -108,17 +109,15 @@ function responseError(err: any) {
return Promise.reject({ message: err }); return Promise.reject({ message: err });
} }
// 有报错响应 // 有报错响应
if (err?.code in TOKEN_ERROR_CODE) { if (err?.code in TOKEN_ERROR_CODE || err?.response?.data?.code in TOKEN_ERROR_CODE) {
if ( if (!['/chat/share', '/chat/team', '/login'].includes(window.location.pathname)) {
!(window.location.pathname === '/chat/share' || window.location.pathname === '/chat/team')
) {
clearToken(); clearToken();
window.location.replace( window.location.replace(
getWebReqUrl(`/login?lastRoute=${encodeURIComponent(location.pathname + location.search)}`) getWebReqUrl(`/login?lastRoute=${encodeURIComponent(location.pathname + location.search)}`)
); );
} }
return Promise.reject({ message: '无权操作' }); return Promise.reject({ message: i18nT('common:unauth_token') });
} }
if ( if (
err?.statusText === TeamErrEnum.aiPointsNotEnough || err?.statusText === TeamErrEnum.aiPointsNotEnough ||

View File

@@ -1,9 +1,8 @@
import { GET, POST, PUT, DELETE } from '@/web/common/api/request'; import { GET, POST, PUT, DELETE } from '@/web/common/api/request';
import { import {
CreatePermissionBody, CollaboratorItemType,
DeletePermissionQuery, DeletePermissionQuery,
ListPermissionResponse, UpdateClbPermissionProps
UpdatePermissionBody
} from '@fastgpt/global/support/permission/collaborator'; } from '@fastgpt/global/support/permission/collaborator';
import { import {
CreateTeamProps, CreateTeamProps,
@@ -43,16 +42,11 @@ export const updateInviteResult = (data: UpdateInviteProps) =>
PUT('/proApi/support/user/team/member/updateInvite', data); PUT('/proApi/support/user/team/member/updateInvite', data);
export const delLeaveTeam = () => DELETE('/proApi/support/user/team/member/leave'); export const delLeaveTeam = () => DELETE('/proApi/support/user/team/member/leave');
export const getTeamClbs = () =>
GET<ListPermissionResponse>(`/proApi/support/user/team/collaborator/list`);
/* -------------- team collaborator -------------------- */ /* -------------- team collaborator -------------------- */
export const updateMemberPermission = (data: UpdatePermissionBody) => export const getTeamClbs = () =>
PUT('/proApi/support/user/team/collaborator/updatePermission', data); GET<CollaboratorItemType[]>(`/proApi/support/user/team/collaborator/list`);
export const updateMemberPermission = (data: UpdateClbPermissionProps) =>
export const createMemberPermission = (data: CreatePermissionBody) => PUT('/proApi/support/user/team/collaborator/update', data);
POST('/proApi/support/user/team/collaborator/create', data);
export const deleteMemberPermission = (id: DeletePermissionQuery) => export const deleteMemberPermission = (id: DeletePermissionQuery) =>
DELETE('/proApi/support/user/team/collaborator/delete', id); DELETE('/proApi/support/user/team/collaborator/delete', id);