({
const tmb = tmbList.find((tmb) => String(tmb._id) === String(item.tmbId));
if (!tmb) return;
+ // @ts-ignore
+ const formatItem = typeof item.toObject === 'function' ? item.toObject() : item;
+
return {
- ...item,
+ ...formatItem,
sourceMember: { name: tmb.name, avatar: tmb.avatar, status: tmb.status }
};
})
diff --git a/packages/web/i18n/en/account_team.json b/packages/web/i18n/en/account_team.json
index 135e72a98d78..b7e1a4744620 100644
--- a/packages/web/i18n/en/account_team.json
+++ b/packages/web/i18n/en/account_team.json
@@ -5,17 +5,23 @@
"7days": "7 Days",
"accept": "accept",
"action": "operate",
+ "assign_permission": "Permission change",
+ "change_department_name": "Department Editor",
+ "change_member_name": "Member name change",
"confirm_delete_group": "Confirm to delete group?",
"confirm_delete_member": "Confirm to delete member?",
"confirm_delete_org": "Confirm to delete organization?",
"confirm_forbidden": "Confirm forbidden",
"confirm_leave_team": "Confirmed to leave the team? \nAfter exiting, all your resources in the team are transferred to the team owner.",
"copy_link": "Copy link",
+ "create_department": "Create a sub-department",
"create_group": "Create group",
"create_invitation_link": "Create Invitation Link",
"create_org": "Create organization",
"create_sub_org": "Create sub-organization",
"delete": "delete",
+ "delete_department": "Delete sub-department",
+ "delete_group": "Delete a group",
"delete_org": "Delete organization",
"edit_info": "Edit information",
"edit_member": "Edit user",
@@ -37,21 +43,51 @@
"invitation_link_list": "Invitation link list",
"invite_member": "Invite members",
"invited": "Invited",
+ "join_team": "Join the team",
+ "kick_out_team": "Remove members",
"label_sync": "Tag sync",
"leave_team_failed": "Leaving the team exception",
+ "log_assign_permission": "[{{name}}] Updated the permissions of [{{objectName}}]: [Application creation: [{{appCreate}}], Knowledge Base: [{{datasetCreate}}], API Key: [{{apiKeyCreate}}], Management: [{{manage}}]]",
+ "log_change_department": "【{{name}}】Updated department【{{departmentName}}】",
+ "log_change_member_name": "【{{name}}】Rename member [{{memberName}}] to 【{{newName}}】",
+ "log_create_department": "【{{name}}】Department【{{departmentName}}】",
+ "log_create_group": "【{{name}}】Created group [{{groupName}}]",
+ "log_create_invitation_link": "【{{name}}】Created invitation link【{{link}}】",
+ "log_delete_department": "{{name}} deleted department {{departmentName}}",
+ "log_delete_group": "{{name}} deleted group {{groupName}}",
+ "log_details": "Details",
+ "log_join_team": "【{{name}}】Join the team through the invitation link 【{{link}}】",
+ "log_kick_out_team": "{{name}} removed member {{memberName}}",
+ "log_login": "【{{name}}】Logined in the system",
+ "log_relocate_department": "【{{name}}】Displayed department【{{departmentName}}】",
+ "log_time": "Operation time",
+ "log_type": "Operation Type",
+ "log_user": "Operator",
+ "login": "Log in",
"manage_member": "Managing members",
"member": "member",
"member_group": "Belonging to member group",
"move_member": "Move member",
"move_org": "Move organization",
+ "operation_log": "log",
"org": "organization",
"org_description": "Organization description",
"org_name": "Organization name",
"owner": "owner",
"permission": "Permissions",
+ "permission_apikeyCreate": "Create API Key",
+ "permission_apikeyCreate_Tip": "Can create global APIKeys",
+ "permission_appCreate": "Create Application",
+ "permission_appCreate_tip": "Can create applications in the root directory (creation permissions in folders are controlled by the folder)",
+ "permission_datasetCreate": "Create Knowledge Base",
+ "permission_datasetCreate_Tip": "Can create knowledge bases in the root directory (creation permissions in folders are controlled by the folder)",
+ "permission_manage": "Admin",
+ "permission_manage_tip": "Can manage members, create groups, manage all groups, and assign permissions to groups and members",
+ "relocate_department": "Department Mobile",
"remark": "remark",
"remove_tip": "Confirm to remove {{username}} from the team?",
"retain_admin_permissions": "Keep administrator rights",
+ "search_log": "Search log",
"search_member_group_name": "Search member/group name",
"total_team_members": "{{amount}} members in total",
"transfer_ownership": "transfer owner",
@@ -61,13 +97,5 @@
"user_team_invite_member": "Invite members",
"user_team_leave_team": "Leave the team",
"user_team_leave_team_failed": "Failure to leave the team",
- "waiting": "To be accepted",
- "permission_appCreate": "Create Application",
- "permission_datasetCreate": "Create Knowledge Base",
- "permission_apikeyCreate": "Create API Key",
- "permission_appCreate_tip": "Can create applications in the root directory (creation permissions in folders are controlled by the folder)",
- "permission_datasetCreate_Tip": "Can create knowledge bases in the root directory (creation permissions in folders are controlled by the folder)",
- "permission_apikeyCreate_Tip": "Can create global APIKeys",
- "permission_manage": "Admin",
- "permission_manage_tip": "Can manage members, create groups, manage all groups, and assign permissions to groups and members"
+ "waiting": "To be accepted"
}
diff --git a/packages/web/i18n/zh-CN/account_team.json b/packages/web/i18n/zh-CN/account_team.json
index c612cbd4ff59..a98318bd4f2e 100644
--- a/packages/web/i18n/zh-CN/account_team.json
+++ b/packages/web/i18n/zh-CN/account_team.json
@@ -5,6 +5,9 @@
"7days": "7天",
"accept": "接受",
"action": "操作",
+ "assign_permission": "权限变更",
+ "change_department_name": "部门编辑",
+ "change_member_name": "成员改名",
"confirm_delete_from_org": "确认将 {{username}} 移出部门?",
"confirm_delete_from_team": "确认将 {{username}} 移出团队?",
"confirm_delete_group": "确认删除群组?",
@@ -12,13 +15,16 @@
"confirm_forbidden": "确认停用",
"confirm_leave_team": "确认离开该团队? \n退出后,您在该团队所有的资源均转让给团队所有者。",
"copy_link": "复制链接",
+ "create_department": "创建子部门",
"create_group": "创建群组",
"create_invitation_link": "创建邀请链接",
"create_org": "创建部门",
"create_sub_org": "创建子部门",
"delete": "删除",
+ "delete_department": "删除子部门",
"delete_from_org": "移出部门",
"delete_from_team": "移出团队",
+ "delete_group": "删除群组",
"delete_org": "删除部门",
"edit_info": "编辑信息",
"edit_member": "编辑用户",
@@ -41,27 +47,37 @@
"invitation_link_list": "链接列表",
"invite_member": "邀请成员",
"invited": "已邀请",
+ "join_team": "加入团队",
"join_update_time": "加入/更新时间",
+ "kick_out_team": "移除成员",
"label_sync": "标签同步",
"leave": "已离职",
"leave_team_failed": "离开团队异常",
+ "log_details": "详情",
+ "log_time": "操作时间",
+ "log_type": "操作类型",
+ "log_user": "操作人员",
+ "login": "登录",
"manage_member": "管理成员",
"member": "成员",
"member_group": "所属群组",
"move_member": "移动成员",
"move_org": "移动部门",
"notification_recieve": "团队通知接收",
+ "operation_log": "日志",
"org": "部门",
"org_description": "介绍",
"org_name": "部门名称",
"owner": "所有者",
"permission": "权限",
"please_bind_contact": "请绑定联系方式",
+ "relocate_department": "部门移动",
"remark": "备注",
"remove_tip": "确认将 {{username}} 移出团队?成员将被标记为“已离职”,不删除操作数据,账号下资源自动转让给团队所有者。",
"restore_tip": "确认将 {{username}} 加入团队吗?仅恢复该成员账号可用性及相关权限,无法恢复账号下资源。",
"restore_tip_title": "恢复确认",
"retain_admin_permissions": "保留管理员权限",
+ "search_log": "搜索日志",
"search_member": "搜索成员",
"search_member_group_name": "搜索成员/群组名称",
"search_org": "搜索部门",
@@ -85,5 +101,17 @@
"permission_datasetCreate_Tip": "可以在根目录创建知识库,(文件夹下的创建权限由文件夹控制)",
"permission_apikeyCreate_Tip": "可以创建全局的 APIKey",
"permission_manage": "管理员",
- "permission_manage_tip": "可以管理成员、创建群组、管理所有群组、为群组和成员分配权限"
+ "permission_manage_tip": "可以管理成员、创建群组、管理所有群组、为群组和成员分配权限",
+ "log_login": "【{{name}}】登录了系统",
+ "log_create_invitation_link": "【{{name}}】创建了邀请链接【{{link}}】",
+ "log_join_team": "【{{name}}】通过邀请链接【{{link}}】加入团队",
+ "log_change_member_name": "【{{name}}】将成员【{{memberName}}】重命名为【{{newName}}】",
+ "log_kick_out_team": "【{{name}}】移除了成员【{{memberName}}】",
+ "log_create_department": "【{{name}}】创建了部门【{{departmentName}}】",
+ "log_change_department": "【{{name}}】更新了部门【{{departmentName}}】",
+ "log_delete_department": "【{{name}}】删除了部门【{{departmentName}}】",
+ "log_relocate_department": "【{{name}}】移动了部门【{{departmentName}}】",
+ "log_create_group": "【{{name}}】创建了群组【{{groupName}}】",
+ "log_delete_group": "【{{name}}】删除了群组【{{groupName}}】",
+ "log_assign_permission": "【{{name}}】更新了【{{objectName}}】的权限:[应用创建:【{{appCreate}}】, 知识库:【{{datasetCreate}}】, API密钥:【{{apiKeyCreate}}】, 管理:【{{manage}}】]"
}
diff --git a/packages/web/i18n/zh-Hant/account_team.json b/packages/web/i18n/zh-Hant/account_team.json
index eac43e6f0d09..8b55a9276d5f 100644
--- a/packages/web/i18n/zh-Hant/account_team.json
+++ b/packages/web/i18n/zh-Hant/account_team.json
@@ -5,17 +5,23 @@
"7days": "7 天",
"accept": "接受",
"action": "操作",
+ "assign_permission": "權限變更",
+ "change_department_name": "部門編輯",
+ "change_member_name": "成員改名",
"confirm_delete_group": "確認刪除群組?",
"confirm_delete_member": "確認刪除成員?",
"confirm_delete_org": "確認刪除該部門?",
"confirm_forbidden": "確認停用",
"confirm_leave_team": "確認離開該團隊? \n結束後,您在該團隊所有的資源轉讓給團隊所有者。",
"copy_link": "複製連結",
+ "create_department": "創建子部門",
"create_group": "建立群組",
"create_invitation_link": "建立邀請連結",
"create_org": "建立部門",
"create_sub_org": "建立子部門",
"delete": "刪除",
+ "delete_department": "刪除子部門",
+ "delete_group": "刪除群組",
"delete_org": "刪除部門",
"edit_info": "編輯訊息",
"edit_member": "編輯使用者",
@@ -37,21 +43,51 @@
"invitation_link_list": "連結列表",
"invite_member": "邀請成員",
"invited": "已邀請",
+ "join_team": "加入團隊",
+ "kick_out_team": "移除成員",
"label_sync": "標籤同步",
"leave_team_failed": "離開團隊異常",
+ "log_assign_permission": "【{{name}}】更新了【{{objectName}}】的權限:[應用創建:【{{appCreate}}】, 知識庫:【{{datasetCreate}}】, API密鑰:【{{apiKeyCreate}}】, 管理:【{{manage}}】]",
+ "log_change_department": "【{{name}}】更新了部門【{{departmentName}}】",
+ "log_change_member_name": "【{{name}}】將成員【{{memberName}}】重命名為【{{newName}}】",
+ "log_create_department": "【{{name}}】創建了部門【{{departmentName}}】",
+ "log_create_group": "【{{name}}】創建了群組【{{groupName}}】",
+ "log_create_invitation_link": "【{{name}}】創建了邀請鏈接【{{link}}】",
+ "log_delete_department": "{{name}} 刪除了部門 {{departmentName}}",
+ "log_delete_group": "{{name}} 刪除了群組 {{groupName}}",
+ "log_details": "詳情",
+ "log_join_team": "【{{name}}】通過邀請鏈接【{{link}}】加入團隊",
+ "log_kick_out_team": "{{name}} 移除了成員 {{memberName}}",
+ "log_login": "【{{name}}】登錄了系統",
+ "log_relocate_department": "【{{name}}】移動了部門【{{departmentName}}】",
+ "log_time": "操作時間",
+ "log_type": "操作類型",
+ "log_user": "操作人員",
+ "login": "登入",
"manage_member": "管理成員",
"member": "成員",
"member_group": "所屬成員組",
"move_member": "移動成員",
"move_org": "行動部門",
+ "operation_log": "紀錄",
"org": "組織",
"org_description": "介紹",
"org_name": "部門名稱",
"owner": "擁有者",
"permission": "權限",
+ "permission_apikeyCreate": "建立 API 密鑰",
+ "permission_apikeyCreate_Tip": "可以建立全域的 APIKey",
+ "permission_appCreate": "建立應用程式",
+ "permission_appCreate_tip": "可以在根目錄建立應用程式,(資料夾下的建立權限由資料夾控制)",
+ "permission_datasetCreate": "建立知識庫",
+ "permission_datasetCreate_Tip": "可以在根目錄建立知識庫,(資料夾下的建立權限由資料夾控制)",
+ "permission_manage": "管理員",
+ "permission_manage_tip": "可以管理成員、建立群組、管理所有群組、為群組和成員分配權限",
+ "relocate_department": "部門移動",
"remark": "備註",
"remove_tip": "確認將 {{username}} 移出團隊?",
"retain_admin_permissions": "保留管理員權限",
+ "search_log": "搜索日誌",
"search_member_group_name": "搜尋成員/群組名稱",
"total_team_members": "共 {{amount}} 名成員",
"transfer_ownership": "轉讓所有者",
@@ -61,13 +97,5 @@
"user_team_invite_member": "邀請成員",
"user_team_leave_team": "離開團隊",
"user_team_leave_team_failed": "離開團隊失敗",
- "waiting": "待接受",
- "permission_appCreate": "建立應用程式",
- "permission_datasetCreate": "建立知識庫",
- "permission_apikeyCreate": "建立 API 密鑰",
- "permission_appCreate_tip": "可以在根目錄建立應用程式,(資料夾下的建立權限由資料夾控制)",
- "permission_datasetCreate_Tip": "可以在根目錄建立知識庫,(資料夾下的建立權限由資料夾控制)",
- "permission_apikeyCreate_Tip": "可以建立全域的 APIKey",
- "permission_manage": "管理員",
- "permission_manage_tip": "可以管理成員、建立群組、管理所有群組、為群組和成員分配權限"
+ "waiting": "待接受"
}
diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/utils.ts b/projects/app/src/components/core/chat/ChatContainer/ChatBox/utils.ts
index b090f38d6431..5eaa3b42ed15 100644
--- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/utils.ts
+++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/utils.ts
@@ -6,6 +6,7 @@ import {
import { ChatBoxInputType, UserInputFileItemType } from './type';
import { getFileIcon } from '@fastgpt/global/common/file/icon';
import { ChatItemValueTypeEnum, ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
+import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils';
export const formatChatValue2InputType = (value?: ChatItemValueItemType[]): ChatBoxInputType => {
if (!value) {
@@ -82,17 +83,19 @@ export const setUserSelectResultToHistories = (
i !== item.value.length - 1 ||
val.type !== ChatItemValueTypeEnum.interactive ||
!val.interactive
- )
+ ) {
return val;
+ }
- if (val.interactive.type === 'userSelect') {
+ const finalInteractive = extractDeepestInteractive(val.interactive);
+ if (finalInteractive.type === 'userSelect') {
return {
...val,
interactive: {
- ...val.interactive,
+ ...finalInteractive,
params: {
- ...val.interactive.params,
- userSelectedVal: val.interactive.params.userSelectOptions.find(
+ ...finalInteractive.params,
+ userSelectedVal: finalInteractive.params.userSelectOptions.find(
(item) => item.value === interactiveVal
)?.value
}
@@ -100,13 +103,13 @@ export const setUserSelectResultToHistories = (
};
}
- if (val.interactive.type === 'userInput') {
+ if (finalInteractive.type === 'userInput') {
return {
...val,
interactive: {
- ...val.interactive,
+ ...finalInteractive,
params: {
- ...val.interactive.params,
+ ...finalInteractive.params,
submitted: true
}
}
diff --git a/projects/app/src/components/core/chat/components/AIResponseBox.tsx b/projects/app/src/components/core/chat/components/AIResponseBox.tsx
index cb3a26a8fa92..09cfff404bd0 100644
--- a/projects/app/src/components/core/chat/components/AIResponseBox.tsx
+++ b/projects/app/src/components/core/chat/components/AIResponseBox.tsx
@@ -28,6 +28,7 @@ import { isEqual } from 'lodash';
import { useTranslation } from 'next-i18next';
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
import { SelectOptionsComponent, FormInputComponent } from './Interactive/InteractiveComponents';
+import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils';
const accordionButtonStyle = {
w: 'auto',
@@ -245,11 +246,12 @@ const AIResponseBox = ({
return ;
}
if (value.type === ChatItemValueTypeEnum.interactive && value.interactive) {
- if (value.interactive.type === 'userSelect') {
- return ;
+ const finalInteractive = extractDeepestInteractive(value.interactive);
+ if (finalInteractive.type === 'userSelect') {
+ return ;
}
- if (value.interactive?.type === 'userInput') {
- return ;
+ if (finalInteractive.type === 'userInput') {
+ return ;
}
}
return null;
diff --git a/projects/app/src/pageComponents/account/team/MemberTable.tsx b/projects/app/src/pageComponents/account/team/MemberTable.tsx
index 425fc6f7ab0d..d60ec4cc601b 100644
--- a/projects/app/src/pageComponents/account/team/MemberTable.tsx
+++ b/projects/app/src/pageComponents/account/team/MemberTable.tsx
@@ -303,7 +303,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
})()}
-
+
{format(new Date(member.createTime), 'yyyy-MM-dd HH:mm:ss')}
{member.updateTime
diff --git a/projects/app/src/pageComponents/account/team/OperationLog/index.tsx b/projects/app/src/pageComponents/account/team/OperationLog/index.tsx
new file mode 100644
index 000000000000..bf85a21ffc00
--- /dev/null
+++ b/projects/app/src/pageComponents/account/team/OperationLog/index.tsx
@@ -0,0 +1,102 @@
+import {
+ Box,
+ Button,
+ Flex,
+ Table,
+ TableContainer,
+ Tbody,
+ Td,
+ Th,
+ Thead,
+ Tr
+} from '@chakra-ui/react';
+import { useState } from 'react';
+import { useTranslation } from 'next-i18next';
+import MyBox from '@fastgpt/web/components/common/MyBox';
+import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
+import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
+import { getOperationLogs } from '@/web/support/user/team/operantionLog/api';
+import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
+import { operationLogI18nMap } from '@fastgpt/service/support/operationLog/constants';
+import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants';
+import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
+import UserBox from '@fastgpt/web/components/common/UserBox';
+
+function OperationLogTable({ Tabs }: { Tabs: React.ReactNode }) {
+ const { t } = useTranslation();
+
+ const [searchKey, setSearchKey] = useState('');
+ const {
+ data: operationLogs = [],
+ isLoading: loadingLogs,
+ ScrollData: LogScrollData
+ } = useScrollPagination(getOperationLogs, {
+ pageSize: 20,
+ refreshDeps: [searchKey],
+ throttleWait: 500,
+ debounceWait: 200
+ });
+
+ const isLoading = loadingLogs;
+
+ return (
+ <>
+
+ {Tabs}
+
+
+
+
+
+
+
+
+ |
+ {t('account_team:log_user')}
+ |
+ {t('account_team:log_time')} |
+ {t('account_team:log_type')} |
+ {t('account_team:log_details')} |
+
+
+
+ {operationLogs?.map((log) => {
+ const i18nData = operationLogI18nMap[log.event];
+ const metadata = { ...log.metadata };
+
+ if (log.event === OperationLogEventEnum.ASSIGN_PERMISSION) {
+ const permissionValue = parseInt(metadata.permission, 10);
+
+ const permission = new TeamPermission({ per: permissionValue });
+ metadata.appCreate = permission.hasAppCreatePer ? '✔' : '✘';
+ metadata.datasetCreate = permission.hasDatasetCreatePer ? '✔' : '✘';
+ metadata.apiKeyCreate = permission.hasApikeyCreatePer ? '✔' : '✘';
+ metadata.manage = permission.hasManagePer ? '✔' : '✘';
+ }
+
+ return i18nData ? (
+
+ |
+
+ |
+ {formatTime2YMDHMS(log.timestamp)} |
+ {t(i18nData.typeLabel)} |
+ {t(i18nData.content, metadata as any) as string} |
+
+ ) : null;
+ })}
+
+
+
+
+
+ >
+ );
+}
+
+export default OperationLogTable;
diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderDebug/NodeDebugResponse.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderDebug/NodeDebugResponse.tsx
index c1065234d8cf..e260eef42ede 100644
--- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderDebug/NodeDebugResponse.tsx
+++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderDebug/NodeDebugResponse.tsx
@@ -13,7 +13,10 @@ import {
SelectOptionsComponent
} from '@/components/core/chat/components/Interactive/InteractiveComponents';
import { UserInputInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
-import { initWorkflowEdgeStatus } from '@fastgpt/global/core/workflow/runtime/utils';
+import {
+ getLastInteractiveValue,
+ initWorkflowEdgeStatus
+} from '@fastgpt/global/core/workflow/runtime/utils';
import { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
@@ -130,10 +133,11 @@ const NodeDebugResponse = ({ nodeId, debugResult }: NodeDebugResponseProps) => {
}
];
+ const lastInteractive = getLastInteractiveValue(mockHistory);
onNextNodeDebug({
...workflowDebugData,
// Rewrite runtimeEdges
- runtimeEdges: initWorkflowEdgeStatus(workflowDebugData.runtimeEdges, mockHistory),
+ runtimeEdges: initWorkflowEdgeStatus(workflowDebugData.runtimeEdges, lastInteractive),
query: updatedQuery,
history: mockHistory
});
diff --git a/projects/app/src/pages/account/team/index.tsx b/projects/app/src/pages/account/team/index.tsx
index 5d613dbb5813..1405016e9d50 100644
--- a/projects/app/src/pages/account/team/index.tsx
+++ b/projects/app/src/pages/account/team/index.tsx
@@ -18,6 +18,7 @@ const MemberTable = dynamic(() => import('@/pageComponents/account/team/MemberTa
const PermissionManage = dynamic(
() => import('@/pageComponents/account/team/PermissionManage/index')
);
+const OperationLogTable = dynamic(() => import('@/pageComponents/account/team/OperationLog/index'));
const GroupManage = dynamic(() => import('@/pageComponents/account/team/GroupManage/index'));
const OrgManage = dynamic(() => import('@/pageComponents/account/team/OrgManage/index'));
const HandleInviteModal = dynamic(
@@ -28,7 +29,8 @@ export enum TeamTabEnum {
member = 'member',
org = 'org',
group = 'group',
- permission = 'permission'
+ permission = 'permission',
+ operationLog = 'operationLog'
}
const Team = () => {
@@ -57,7 +59,8 @@ const Team = () => {
{ label: t('account_team:member'), value: TeamTabEnum.member },
{ label: t('account_team:org'), value: TeamTabEnum.org },
{ label: t('account_team:group'), value: TeamTabEnum.group },
- { label: t('account_team:permission'), value: TeamTabEnum.permission }
+ { label: t('account_team:permission'), value: TeamTabEnum.permission },
+ { label: t('account_team:operation_log'), value: TeamTabEnum.operationLog }
]}
px={'1rem'}
value={teamTab}
@@ -150,6 +153,7 @@ const Team = () => {
{teamTab === TeamTabEnum.org && }
{teamTab === TeamTabEnum.group && }
{teamTab === TeamTabEnum.permission && }
+ {teamTab === TeamTabEnum.operationLog && }
{invitelinkid && }
diff --git a/projects/app/src/pages/api/core/chat/chatTest.ts b/projects/app/src/pages/api/core/chat/chatTest.ts
index 402ea353ef8d..2570aca57d0e 100644
--- a/projects/app/src/pages/api/core/chat/chatTest.ts
+++ b/projects/app/src/pages/api/core/chat/chatTest.ts
@@ -98,7 +98,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
const isPlugin = app.type === AppTypeEnum.plugin;
- const userQuestion: UserChatItemType = (() => {
+ const userQuestion: UserChatItemType = await (async () => {
if (isPlugin) {
return getPluginRunUserQuery({
pluginInputs: getPluginInputsFromStoreNodes(app.modules),
@@ -107,9 +107,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
});
}
- const latestHumanChat = chatMessages.pop() as UserChatItemType | undefined;
+ const latestHumanChat = chatMessages.pop() as UserChatItemType;
if (!latestHumanChat) {
- throw new Error('User question is empty');
+ return Promise.reject('User question is empty');
}
return latestHumanChat;
})();
@@ -136,14 +136,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}
const newHistories = concatHistories(histories, chatMessages);
-
+ const interactive = getLastInteractiveValue(newHistories) || undefined;
// Get runtimeNodes
- let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, newHistories));
+ let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, interactive));
if (isPlugin) {
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
variables = {};
}
- runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
+ runtimeNodes = rewriteNodeOutputByHistories(runtimeNodes, interactive);
const workflowResponseWrite = getWorkflowResponseWrite({
res,
@@ -175,9 +175,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
chatId,
responseChatItemId,
runtimeNodes,
- runtimeEdges: initWorkflowEdgeStatus(edges, newHistories),
+ runtimeEdges: initWorkflowEdgeStatus(edges, interactive),
variables,
query: removeEmptyUserInput(userQuestion.value),
+ lastInteractive: interactive,
chatConfig,
histories: newHistories,
stream: true,
diff --git a/projects/app/src/pages/api/core/workflow/debug.ts b/projects/app/src/pages/api/core/workflow/debug.ts
index d7fad05465e3..e4fee5f93088 100644
--- a/projects/app/src/pages/api/core/workflow/debug.ts
+++ b/projects/app/src/pages/api/core/workflow/debug.ts
@@ -10,6 +10,7 @@ import { NextAPI } from '@/service/middleware/entry';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { defaultApp } from '@/web/core/app/constants';
import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants';
+import { getLastInteractiveValue } from '@fastgpt/global/core/workflow/runtime/utils';
async function handler(
req: NextApiRequest,
@@ -44,6 +45,7 @@ async function handler(
// auth balance
const { timezone, externalProvider } = await getUserChatInfoAndAuthTeamPoints(tmbId);
+ const lastInteractive = getLastInteractiveValue(history);
/* start process */
const { flowUsages, flowResponses, debugResponse, newVariables, workflowInteractiveResponse } =
@@ -65,6 +67,7 @@ async function handler(
},
runtimeNodes: nodes,
runtimeEdges: edges,
+ lastInteractive,
variables,
query: query,
chatConfig: defaultApp.chatConfig,
diff --git a/projects/app/src/pages/api/support/user/account/loginByPassword.ts b/projects/app/src/pages/api/support/user/account/loginByPassword.ts
index 8c37ce0d2d74..14652df36853 100644
--- a/projects/app/src/pages/api/support/user/account/loginByPassword.ts
+++ b/projects/app/src/pages/api/support/user/account/loginByPassword.ts
@@ -9,6 +9,8 @@ import { useIPFrequencyLimit } from '@fastgpt/service/common/middle/reqFrequency
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
+import { addOperationLog } from '@fastgpt/service/support/operationLog/addOperationLog';
+import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants';
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { username, password } = req.body as PostLoginProps;
@@ -64,6 +66,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
setCookie(res, token);
+ addOperationLog({
+ tmbId: userDetail.team.tmbId,
+ teamId: userDetail.team.teamId,
+ event: OperationLogEventEnum.LOGIN
+ });
+
return {
user: userDetail,
token
diff --git a/projects/app/src/pages/api/v1/chat/completions.ts b/projects/app/src/pages/api/v1/chat/completions.ts
index 5657a2783da5..f0a352d9fa9b 100644
--- a/projects/app/src/pages/api/v1/chat/completions.ts
+++ b/projects/app/src/pages/api/v1/chat/completions.ts
@@ -139,7 +139,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// Computed start hook params
const startHookText = (() => {
// Chat
- const userQuestion = chatMessages[chatMessages.length - 1] as UserChatItemType | undefined;
+ const userQuestion = chatMessages[chatMessages.length - 1] as UserChatItemType;
if (userQuestion) return chatValue2RuntimePrompt(userQuestion.value).text;
// plugin
@@ -245,16 +245,17 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// Get chat histories
const newHistories = concatHistories(histories, chatMessages);
+ const interactive = getLastInteractiveValue(newHistories) || undefined;
// Get runtimeNodes
- let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, newHistories));
+ let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, interactive));
if (isPlugin) {
// Assign values to runtimeNodes using variables
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
// Plugin runtime does not need global variables(It has been injected into the pluginInputNode)
variables = {};
}
- runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
+ runtimeNodes = rewriteNodeOutputByHistories(runtimeNodes, interactive);
const workflowResponseWrite = getWorkflowResponseWrite({
res,
@@ -288,7 +289,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
chatId,
responseChatItemId,
runtimeNodes,
- runtimeEdges: initWorkflowEdgeStatus(edges, newHistories),
+ runtimeEdges: initWorkflowEdgeStatus(edges, interactive),
variables,
query: removeEmptyUserInput(userQuestion.value),
chatConfig,
diff --git a/projects/app/src/pages/api/v2/chat/completions.ts b/projects/app/src/pages/api/v2/chat/completions.ts
index 4750ee07cdea..75e336881428 100644
--- a/projects/app/src/pages/api/v2/chat/completions.ts
+++ b/projects/app/src/pages/api/v2/chat/completions.ts
@@ -139,7 +139,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// Computed start hook params
const startHookText = (() => {
// Chat
- const userQuestion = chatMessages[chatMessages.length - 1] as UserChatItemType | undefined;
+ const userQuestion = chatMessages[chatMessages.length - 1] as UserChatItemType;
if (userQuestion) return chatValue2RuntimePrompt(userQuestion.value).text;
// plugin
@@ -245,16 +245,16 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// Get chat histories
const newHistories = concatHistories(histories, chatMessages);
-
+ const interactive = getLastInteractiveValue(newHistories) || undefined;
// Get runtimeNodes
- let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, newHistories));
+ let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, interactive));
if (isPlugin) {
// Assign values to runtimeNodes using variables
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
// Plugin runtime does not need global variables(It has been injected into the pluginInputNode)
variables = {};
}
- runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
+ runtimeNodes = rewriteNodeOutputByHistories(runtimeNodes, interactive);
const workflowResponseWrite = getWorkflowResponseWrite({
res,
@@ -288,9 +288,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
chatId,
responseChatItemId,
runtimeNodes,
- runtimeEdges: initWorkflowEdgeStatus(edges, newHistories),
+ runtimeEdges: initWorkflowEdgeStatus(edges, interactive),
variables,
query: removeEmptyUserInput(userQuestion.value),
+ lastInteractive: interactive,
chatConfig,
histories: newHistories,
stream,
diff --git a/projects/app/src/service/events/generateQA.ts b/projects/app/src/service/events/generateQA.ts
index ebf8a829cd4c..69a5b408914e 100644
--- a/projects/app/src/service/events/generateQA.ts
+++ b/projects/app/src/service/events/generateQA.ts
@@ -33,9 +33,21 @@ const reduceQueue = () => {
return global.qaQueueLen === 0;
};
+const reduceQueueAndReturn = (delay = 0) => {
+ reduceQueue();
+ if (delay) {
+ setTimeout(() => {
+ generateQA();
+ }, delay);
+ } else {
+ generateQA();
+ }
+};
export async function generateQA(): Promise {
const max = global.systemEnv?.qaMaxProcess || 10;
+ addLog.debug(`[QA Queue] Queue size: ${global.qaQueueLen}`);
+
if (global.qaQueueLen >= max) return;
global.qaQueueLen++;
@@ -98,14 +110,12 @@ export async function generateQA(): Promise {
return;
}
if (error) {
- reduceQueue();
- return generateQA();
+ return reduceQueueAndReturn();
}
// auth balance
if (!(await checkTeamAiPointsAndLock(data.teamId))) {
- reduceQueue();
- return generateQA();
+ return reduceQueueAndReturn();
}
addLog.info(`[QA Queue] Start`);
@@ -137,14 +147,8 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
const qaArr = formatSplitText({ answer, rawText: text, llmModel: modelData }); // 格式化后的QA对
- addLog.info(`[QA Queue] Finish`, {
- time: Date.now() - startTime,
- splitLength: qaArr.length,
- usage: chatResponse.usage
- });
-
// get vector and insert
- const { insertLen } = await pushDataListToTrainingQueueByCollectionId({
+ await pushDataListToTrainingQueueByCollectionId({
teamId: data.teamId,
tmbId: data.tmbId,
collectionId: data.collectionId,
@@ -160,21 +164,21 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
await MongoDatasetTraining.findByIdAndDelete(data._id);
// add bill
- if (insertLen > 0) {
- pushQAUsage({
- teamId: data.teamId,
- tmbId: data.tmbId,
- inputTokens: await countGptMessagesTokens(messages),
- outputTokens: await countPromptTokens(answer),
- billId: data.billId,
- model: modelData.model
- });
- } else {
- addLog.info(`QA result 0:`, { answer });
- }
+ pushQAUsage({
+ teamId: data.teamId,
+ tmbId: data.tmbId,
+ inputTokens: await countGptMessagesTokens(messages),
+ outputTokens: await countPromptTokens(answer),
+ billId: data.billId,
+ model: modelData.model
+ });
+ addLog.info(`[QA Queue] Finish`, {
+ time: Date.now() - startTime,
+ splitLength: qaArr.length,
+ usage: chatResponse.usage
+ });
- reduceQueue();
- generateQA();
+ return reduceQueueAndReturn();
} catch (err: any) {
addLog.error(`[QA Queue] Error`, err);
await MongoDatasetTraining.updateOne(
@@ -188,9 +192,7 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
}
);
- setTimeout(() => {
- generateQA();
- }, 1000);
+ return reduceQueueAndReturn(1000);
}
}
diff --git a/projects/app/src/service/events/generateVector.ts b/projects/app/src/service/events/generateVector.ts
index 658e5618efd1..c488cbc62cca 100644
--- a/projects/app/src/service/events/generateVector.ts
+++ b/projects/app/src/service/events/generateVector.ts
@@ -35,6 +35,8 @@ const reduceQueueAndReturn = (delay = 0) => {
/* 索引生成队列。每导入一次,就是一个单独的线程 */
export async function generateVector(): Promise {
const max = global.systemEnv?.vectorMaxProcess || 10;
+ addLog.debug(`[Vector Queue] Queue size: ${global.vectorQueueLen}`);
+
if (global.vectorQueueLen >= max) return;
global.vectorQueueLen++;
const start = Date.now();
diff --git a/projects/app/src/web/support/user/team/operantionLog/api.ts b/projects/app/src/web/support/user/team/operantionLog/api.ts
new file mode 100644
index 000000000000..728d3b0df4eb
--- /dev/null
+++ b/projects/app/src/web/support/user/team/operantionLog/api.ts
@@ -0,0 +1,9 @@
+import { GET, POST, PUT } from '@/web/common/api/request';
+import type { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
+import type { OperationListItemType } from '@fastgpt/global/support/operationLog/type';
+
+export const getOperationLogs = (props: PaginationProps) =>
+ POST>(
+ `/proApi/support/user/team/operationLog/list`,
+ props
+ );
|