diff --git a/docSite/content/zh-cn/docs/development/upgrading/489.md b/docSite/content/zh-cn/docs/development/upgrading/489.md
index 1551e28d2..95f470ebe 100644
--- a/docSite/content/zh-cn/docs/development/upgrading/489.md
+++ b/docSite/content/zh-cn/docs/development/upgrading/489.md
@@ -36,13 +36,14 @@ curl --location --request POST 'https://{{host}}/api/admin/init/489' \
4. 商业版新增 - 团队通知账号绑定,用于接收重要信息。
5. 商业版新增 - 知识库集合标签功能,可以对知识库进行标签管理。
6. 商业版新增 - 知识库搜索节点支持标签过滤和创建时间过滤。
-7. 新增 - 删除所有对话引导内容。
-8. 优化 - 对话框信息懒加载,减少网络传输。
-9. 优化 - 清除选文件缓存,支持重复选择同一个文件。
-10. 修复 - 知识库上传文件,网络不稳定或文件较多情况下,进度无法到 100%。
-11. 修复 - 删除应用后回到聊天选择最后一次对话的应用为删除的应用时提示无该应用问题。
-12. 修复 - 插件动态变量配置默认值时,无法正常显示默认值。
-13. 修复 - 工具调用温度和最大回复值未生效。
-14. 修复 - 函数调用模式,assistant role 中,GPT 模型必须传入 content 参数。(不影响大部分模型,目前基本都改用用 ToolChoice 模式,FC 模式已弃用)。
-15. 修复 - 知识库文件上传进度更新可能异常。
-16. 修复 - 知识库 rebuilding 时候,页面总是刷新到第一页。
+7. 商业版新增 - 转移 App owner 权限。
+8. 新增 - 删除所有对话引导内容。
+9. 优化 - 对话框信息懒加载,减少网络传输。
+10. 优化 - 清除选文件缓存,支持重复选择同一个文件。
+11. 修复 - 知识库上传文件,网络不稳定或文件较多情况下,进度无法到 100%。
+12. 修复 - 删除应用后回到聊天选择最后一次对话的应用为删除的应用时提示无该应用问题。
+13. 修复 - 插件动态变量配置默认值时,无法正常显示默认值。
+14. 修复 - 工具调用温度和最大回复值未生效。
+15. 修复 - 函数调用模式,assistant role 中,GPT 模型必须传入 content 参数。(不影响大部分模型,目前基本都改用用 ToolChoice 模式,FC 模式已弃用)。
+16. 修复 - 知识库文件上传进度更新可能异常。
+17. 修复 - 知识库 rebuilding 时候,页面总是刷新到第一页。
diff --git a/packages/global/common/error/code/app.ts b/packages/global/common/error/code/app.ts
index 65be4da30..655e003dd 100644
--- a/packages/global/common/error/code/app.ts
+++ b/packages/global/common/error/code/app.ts
@@ -3,7 +3,9 @@ import { i18nT } from '../../../../web/i18n/utils';
/* dataset: 502000 */
export enum AppErrEnum {
unExist = 'appUnExist',
- unAuthApp = 'unAuthApp'
+ unAuthApp = 'unAuthApp',
+ invalidOwner = 'invalidOwner',
+ invalidAppType = 'invalidAppType'
}
const appErrList = [
{
@@ -13,6 +15,14 @@ const appErrList = [
{
statusText: AppErrEnum.unAuthApp,
message: i18nT('common:code_error.app_error.un_auth_app')
+ },
+ {
+ statusText: AppErrEnum.invalidOwner,
+ message: i18nT('common:code_error.app_error.invalid_owner')
+ },
+ {
+ statusText: AppErrEnum.invalidAppType,
+ message: i18nT('common:code_error.app_error.invalid_app_type')
}
];
export default appErrList.reduce((acc, cur, index) => {
diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts
index 8bb7e1f34..ffa3cf15b 100644
--- a/packages/web/components/common/Icon/constants.ts
+++ b/packages/web/components/common/Icon/constants.ts
@@ -34,6 +34,7 @@ export const iconPaths = {
'common/language/en': () => import('./icons/common/language/en.svg'),
'common/language/zh': () => import('./icons/common/language/zh.svg'),
'common/leftArrowLight': () => import('./icons/common/leftArrowLight.svg'),
+ 'common/lineChange': () => import('./icons/common/lineChange.svg'),
'common/linkBlue': () => import('./icons/common/linkBlue.svg'),
'common/loading': () => import('./icons/common/loading.svg'),
'common/logLight': () => import('./icons/common/logLight.svg'),
@@ -241,6 +242,7 @@ export const iconPaths = {
menu: () => import('./icons/menu.svg'),
minus: () => import('./icons/minus.svg'),
'modal/AddClb': () => import('./icons/modal/AddClb.svg'),
+ 'modal/changePer': () => import('./icons/modal/changePer.svg'),
'modal/concat': () => import('./icons/modal/concat.svg'),
'modal/confirmPay': () => import('./icons/modal/confirmPay.svg'),
'modal/edit': () => import('./icons/modal/edit.svg'),
diff --git a/packages/web/components/common/Icon/icons/common/lineChange.svg b/packages/web/components/common/Icon/icons/common/lineChange.svg
new file mode 100644
index 000000000..4db9b27d1
--- /dev/null
+++ b/packages/web/components/common/Icon/icons/common/lineChange.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/web/components/common/Icon/icons/modal/changePer.svg b/packages/web/components/common/Icon/icons/modal/changePer.svg
new file mode 100644
index 000000000..5f4c06109
--- /dev/null
+++ b/packages/web/components/common/Icon/icons/modal/changePer.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/packages/web/i18n/zh/common.json b/packages/web/i18n/zh/common.json
index 2f5349ecb..fbb8d9c63 100644
--- a/packages/web/i18n/zh/common.json
+++ b/packages/web/i18n/zh/common.json
@@ -60,7 +60,9 @@
"code_error": {
"app_error": {
"not_exist": "应用不存在",
- "un_auth_app": "无权操作该应用"
+ "un_auth_app": "无权操作该应用",
+ "invalid_owner": "非法的应用所有者",
+ "invalid_app_type": "错误的应用类型"
},
"chat_error": {
"un_auth": "没有权限操作此对话记录"
@@ -1095,7 +1097,13 @@
"Remove InheritPermission Confirm": "此操作会导致权限继承失效,是否进行?",
"Resume InheritPermission Confirm": "是否恢复为继承父级文件夹的权限?",
"Resume InheritPermission Failed": "恢复失败",
- "Resume InheritPermission Success": "恢复成功"
+ "Resume InheritPermission Success": "恢复成功",
+ "change_owner": "转移所有权",
+ "change_owner_to": "转移给",
+ "change_owner_placeholder": "输入用户名查找账号",
+ "change_owner_tip": "转移后将保留您的管理员权限",
+ "change_owner_success": "成功转移所有权",
+ "change_owner_failed": "转移所有权失败"
},
"plugin": {
"App": "选择应用",
diff --git a/projects/app/src/components/support/permission/ChangeOwnerModal/index.tsx b/projects/app/src/components/support/permission/ChangeOwnerModal/index.tsx
new file mode 100644
index 000000000..3f5e30876
--- /dev/null
+++ b/projects/app/src/components/support/permission/ChangeOwnerModal/index.tsx
@@ -0,0 +1,162 @@
+import { getTeamMembers } from '@/web/support/user/team/api';
+import {
+ Box,
+ Flex,
+ HStack,
+ Input,
+ ModalBody,
+ ModalFooter,
+ Button,
+ useDisclosure
+} from '@chakra-ui/react';
+import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
+import Avatar from '@fastgpt/web/components/common/Avatar';
+import Icon from '@fastgpt/web/components/common/Icon';
+import MyModal from '@fastgpt/web/components/common/MyModal';
+import MyTag from '@fastgpt/web/components/common/Tag';
+import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
+import { useTranslation } from 'next-i18next';
+import React, { useState } from 'react';
+
+export type ChangeOwnerModalProps = {
+ avatar?: string;
+ name: string;
+ onChangeOwner: (tmbId: string) => Promise;
+};
+
+export function ChangeOwnerModal({
+ onClose,
+ avatar,
+ name,
+ onChangeOwner
+}: ChangeOwnerModalProps & { onClose: () => void }) {
+ const { t } = useTranslation();
+ const [inputValue, setInputValue] = React.useState('');
+ const { data: teamMembers = [] } = useRequest2(getTeamMembers, {
+ manual: false
+ });
+
+ const memberList = teamMembers.filter((item) => {
+ return item.memberName.includes(inputValue);
+ });
+
+ const {
+ isOpen: isOpenMemberListMenu,
+ onClose: onCloseMemberListMenu,
+ onOpen: onOpenMemberListMenu
+ } = useDisclosure();
+ const [selectedMember, setSelectedMember] = useState(null);
+
+ const { runAsync, loading } = useRequest2(onChangeOwner, {
+ onSuccess: onClose,
+ successToast: t('common:permission.change_owner_success'),
+ errorToast: t('common:permission.change_owner_failed')
+ });
+
+ const onConfirm = async () => {
+ if (!selectedMember) {
+ return;
+ }
+ await runAsync(selectedMember.tmbId);
+ };
+
+ return (
+
+
+
+
+ {name}
+
+
+
+ {t('common:permission.change_owner_to')}
+
+
+ {selectedMember && (
+
+ )}
+ {
+ setInputValue(e.target.value);
+ setSelectedMember(null);
+ }}
+ onFocus={() => {
+ onOpenMemberListMenu();
+ setSelectedMember(null);
+ }}
+ // onBlur={() => {
+ // setTimeout(() => {
+ // onCloseMemberListMenu();
+ // }, 10);
+ // }}
+ {...(selectedMember && { pl: '10' })}
+ />
+
+ {isOpenMemberListMenu && memberList.length > 0 && (
+
+ {memberList.map((item) => (
+ {
+ setInputValue(item.memberName);
+ setSelectedMember(item);
+ onCloseMemberListMenu();
+ }}
+ >
+
+
+ {item.memberName}
+
+
+ ))}
+
+ )}
+
+
+
+ {t('common:permission.change_owner_tip')}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/projects/app/src/components/support/permission/ConfigPerModal/index.tsx b/projects/app/src/components/support/permission/ConfigPerModal/index.tsx
index 4da8e61ba..73cad5e19 100644
--- a/projects/app/src/components/support/permission/ConfigPerModal/index.tsx
+++ b/projects/app/src/components/support/permission/ConfigPerModal/index.tsx
@@ -3,13 +3,12 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import CollaboratorContextProvider, { MemberManagerInputPropsType } from '../MemberManager/context';
-import { Box, Button, Flex, HStack, ModalBody } from '@chakra-ui/react';
+import { Box, Button, Flex, HStack, ModalBody, useDisclosure } from '@chakra-ui/react';
import Avatar from '@fastgpt/web/components/common/Avatar';
import DefaultPermissionList from '../DefaultPerList';
import MyIcon from '@fastgpt/web/components/common/Icon';
-import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
-import { useI18n } from '@/web/context/I18n';
import ResumeInherit from '../ResumeInheritText';
+import { ChangeOwnerModal } from '../ChangeOwnerModal';
export type ConfigPerModalProps = {
avatar?: string;
@@ -25,6 +24,7 @@ export type ConfigPerModalProps = {
resumeInheritPermission?: () => void;
hasParent?: boolean;
refetchResource?: () => void;
+ onChangeOwner?: (tmbId: string) => Promise;
};
const ConfigPerModal = ({
@@ -36,11 +36,17 @@ const ConfigPerModal = ({
resumeInheritPermission,
hasParent,
onClose,
- refetchResource
+ refetchResource,
+ onChangeOwner
}: ConfigPerModalProps & {
onClose: () => void;
}) => {
const { t } = useTranslation();
+ const {
+ isOpen: isChangeOwnerModalOpen,
+ onOpen: onOpenChangeOwnerModal,
+ onClose: onCloseChangeOwnerModal
+ } = useDisclosure();
return (
<>
@@ -113,8 +119,30 @@ const ConfigPerModal = ({
}}
+ {onChangeOwner && (
+
+ }
+ >
+ {t('common:permission.change_owner')}
+
+
+ )}
+ {isChangeOwnerModalOpen && onChangeOwner && (
+
+ )}
>
);
};
diff --git a/projects/app/src/global/core/app/api.d.ts b/projects/app/src/global/core/app/api.d.ts
index a8b3b56ef..f1c532439 100644
--- a/projects/app/src/global/core/app/api.d.ts
+++ b/projects/app/src/global/core/app/api.d.ts
@@ -29,3 +29,8 @@ export type PostRevertAppProps = {
editEdges: AppSchema['edges'];
editChatConfig: AppSchema['chatConfig'];
};
+
+export type AppChangeOwnerBody = {
+ appId: string;
+ ownerId: string;
+};
diff --git a/projects/app/src/pages/app/detail/components/InfoModal.tsx b/projects/app/src/pages/app/detail/components/InfoModal.tsx
index 8c1e93a95..87db7dc3e 100644
--- a/projects/app/src/pages/app/detail/components/InfoModal.tsx
+++ b/projects/app/src/pages/app/detail/components/InfoModal.tsx
@@ -7,7 +7,8 @@ import {
Input,
Textarea,
ModalFooter,
- ModalBody
+ ModalBody,
+ useDisclosure
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
@@ -15,7 +16,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { compressImgFileAndUpload } from '@/web/common/file/controller';
import { getErrText } from '@fastgpt/global/common/error/utils';
-import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
+import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import Avatar from '@fastgpt/web/components/common/Avatar';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
@@ -35,7 +36,6 @@ import {
import DefaultPermissionList from '@/components/support/permission/DefaultPerList';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { UpdateClbPermissionProps } from '@fastgpt/global/support/permission/collaborator';
-import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { resumeInheritPer } from '@/web/core/app/api';
import { useI18n } from '@/web/context/I18n';
import ResumeInherit from '@/components/support/permission/ResumeInheritText';
@@ -61,6 +61,7 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
defaultValues: appDetail
});
const avatar = getValues('avatar');
+ const name = getValues('name');
// submit config
const { runAsync: saveSubmitSuccess, loading: btnLoading } = useRequest2(
@@ -135,6 +136,7 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
appId: appDetail._id
});
};
+
const onDelCollaborator = async (tmbId: string) => {
await deleteAppCollaborators({
appId: appDetail._id,
diff --git a/projects/app/src/pages/app/detail/index.tsx b/projects/app/src/pages/app/detail/index.tsx
index 1ea21c1e2..a870a0d28 100644
--- a/projects/app/src/pages/app/detail/index.tsx
+++ b/projects/app/src/pages/app/detail/index.tsx
@@ -53,7 +53,7 @@ const Provider = () => {
export async function getServerSideProps(context: any) {
return {
props: {
- ...(await serviceSideProps(context, ['app', 'chat', 'file', 'publish', 'workflow']))
+ ...(await serviceSideProps(context, ['app', 'chat', 'user', 'file', 'publish', 'workflow']))
}
};
}
diff --git a/projects/app/src/pages/app/list/components/List.tsx b/projects/app/src/pages/app/list/components/List.tsx
index eb59e51ad..74a05c49e 100644
--- a/projects/app/src/pages/app/list/components/List.tsx
+++ b/projects/app/src/pages/app/list/components/List.tsx
@@ -1,7 +1,7 @@
import React, { useMemo, useState } from 'react';
import { Box, Grid, Flex, IconButton, HStack } from '@chakra-ui/react';
import { useRouter } from 'next/router';
-import { delAppById, putAppById, resumeInheritPer } from '@/web/core/app/api';
+import { delAppById, putAppById, resumeInheritPer, changeOwner } from '@/web/core/app/api';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@fastgpt/web/components/common/Avatar';
@@ -399,6 +399,12 @@ const ListItem = () => {
)}
{!!editPerApp && (
+ changeOwner({
+ appId: editPerApp._id,
+ ownerId: tmbId
+ }).then(() => loadMyApps())
+ }
refetchResource={loadMyApps}
hasParent={Boolean(parentId)}
resumeInheritPermission={onResumeInheritPermission}
diff --git a/projects/app/src/pages/app/list/index.tsx b/projects/app/src/pages/app/list/index.tsx
index 20b9073fa..920abea1e 100644
--- a/projects/app/src/pages/app/list/index.tsx
+++ b/projects/app/src/pages/app/list/index.tsx
@@ -326,7 +326,7 @@ export default ContextRender;
export async function getServerSideProps(content: any) {
return {
props: {
- ...(await serviceSideProps(content, ['app']))
+ ...(await serviceSideProps(content, ['app', 'user']))
}
};
}
diff --git a/projects/app/src/web/core/app/api.ts b/projects/app/src/web/core/app/api.ts
index ffcd76da0..c44832c62 100644
--- a/projects/app/src/web/core/app/api.ts
+++ b/projects/app/src/web/core/app/api.ts
@@ -1,7 +1,7 @@
import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
import type { AppDetailType, AppListItemType } from '@fastgpt/global/core/app/type.d';
import type { GetAppChatLogsParams } from '@/global/core/api/appReq.d';
-import { AppUpdateParams } from '@/global/core/app/api';
+import { AppUpdateParams, AppChangeOwnerBody } from '@/global/core/app/api';
import type { CreateAppBody } from '@/pages/api/core/app/create';
import type { ListAppBody } from '@/pages/api/core/app/list';
@@ -40,3 +40,5 @@ export const getAppChatLogs = (data: GetAppChatLogsParams) => POST(`/core/app/ge
export const resumeInheritPer = (appId: string) =>
GET(`/core/app/resumeInheritPermission`, { appId });
+
+export const changeOwner = (data: AppChangeOwnerBody) => POST(`/proApi/core/app/changeOwner`, data);