mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-20 10:45:52 +00:00
V4.9.5 feature (#4520)
* readme * Add queue log * Test interactive (#4509) * Support nested node interaction (#4503) * feat: Add a new InteractiveContext type and update InteractiveBasicType, adding an optional context property to support more complex interaction state management. * feat: Enhance workflow interactivity by adding InteractiveContext support and updating dispatch logic to manage nested contexts and entry nodes more effectively. * feat: Refactor dispatchWorkFlow to utilize InteractiveContext for improved context management * feat: Enhance entry node resolution by adding validation for entryNodeIds and recursive search in InteractiveContext * feat: Remove workflowDepth from InteractiveContext and update recovery logic to utilize parentContext for improved context management * feat: Update getWorkflowEntryNodeIds to use lastInteractive for improved context handling in runtime nodes * feat: Add lastInteractive support to enhance context management across workflow components * feat: Enhance interactive workflow by adding stopForInteractive flag and improving memory edge validation in runtime logic * feat: Refactor InteractiveContext by removing interactiveAppId and updating runtime edge handling in dispatchRunApp for improved context management * feat: Simplify runtime node and edge initialization in dispatchRunApp by using ternary operators for improved readability and maintainability * feat: Improve memory edge validation in initWorkflowEdgeStatus by adding detailed comments for better understanding of subset checks and recursive context searching * feat: Remove commented-out current level information from InteractiveContext for cleaner code and improved readability * feat: Simplify stopForInteractive check in dispatchWorkFlow for improved code clarity and maintainability * feat: Remove stopForInteractive handling and related references for improved code clarity and maintainability * feat: Add interactive response handling in dispatchRunAppNode for enhanced workflow interactivity * feat: Add context property to InteractiveBasicType and InteractiveNodeType for improved interactivity management * feat: remove comments * feat: Remove the node property from ChatDispatchProps to simplify type definitions * feat: Remove workflowInteractiveResponse from dispatchRunAppNode for cleaner code * feat: Refactor interactive value handling in chat history processing for improved clarity * feat: Simplify initWorkflowEdgeStatus logic for better readability and maintainability * feat: Add workflowInteractiveResponse to dispatchWorkFlow for enhanced functionality * feat: Enhance interactive response handling with nested children support * feat: Remove commented-out code for interactive node handling to improve clarity * feat: remove InteractiveContext type * feat: Refactor UserSelectInteractive and UserInputInteractive params for improved structure and clarity * feat: remove * feat: The front end supports extracting the deepest interaction parameters to enhance interaction processing * feat: The front end supports extracting the deepest interaction parameters to enhance interaction processing * fix: handle undefined interactive values in runtimeEdges and runtimeNodes initialization * fix: handle undefined interactive values in runtimeNodes and runtimeEdges initialization * fix: update runtimeNodes and runtimeEdges initialization to use last interactive value * fix: remove unused imports and replace getLastInteractiveValue with lastInteractive in runtimeEdges initialization * fix: import WorkflowInteractiveResponseType and handle lastInteractive as undefined in chatTest * feat: implement extractDeepestInteractive function and refactor usage in AIResponseBox and ChatBox utils * fix: refactor initWorkflowEdgeStatus and getWorkflowEntryNodeIds calls in dispatchRunAppNode for recovery handling * fix: ensure lastInteractive is handled consistently as undefined in runtimeEdges and runtimeNodes initialization * fix: update dispatchFormInput and dispatchUserSelect to use lastInteractive consistently * fix: update condition checks in dispatchFormInput and dispatchUserSelect to ensure lastInteractive type is validated correctly * fix: refactor dispatchRunAppNode to replace isRecovery with childrenInteractive for improved clarity in runtimeNodes and runtimeEdges initialization * refactor: streamline runtimeNodes and runtimeEdges initialization in dispatchRunAppNode for improved readability and maintainability * fix: update rewriteNodeOutputByHistories function to accept runtimeNodes and interactive as parameters for improved clarity * fix: simplify interactiveResponse assignment in dispatchWorkFlow for improved clarity * fix: update entryNodeIds check in getWorkflowEntryNodeIds to ensure it's an array for improved reliability * remove some invalid code --------- Co-authored-by: Theresa <63280168+sd0ric4@users.noreply.github.com> * update doc * update log * fix: update debug workflow to conditionally include nextStepSkipNodes… (#4511) * fix: update debug workflow to conditionally include nextStepSkipNodes based on lastInteractive for improved debugging accuracy * fix : type error * remove invalid code * fix: QA queue * fix: interactive * Test log (#4519) * add log (#4504) * add log * update log i18n * update log * delete template * add i18NT * add team operation log --------- Co-authored-by: gggaaallleee <91131304+gggaaallleee@users.noreply.github.com> * remove search * update doc --------- Co-authored-by: Theresa <63280168+sd0ric4@users.noreply.github.com> Co-authored-by: gggaaallleee <91131304+gggaaallleee@users.noreply.github.com>
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
<a href="./README_ja.md">日语</a>
|
<a href="./README_ja.md">日语</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景!
|
FastGPT 是一个 AI Agent 构建平台,提供开箱即用的数据处理、模型调用等能力,同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的应用场景!
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -11,10 +11,18 @@ weight: 795
|
|||||||
## 🚀 新增内容
|
## 🚀 新增内容
|
||||||
|
|
||||||
1. 团队成员权限细分,可分别控制是否可创建在根目录应用/知识库以及 API Key
|
1. 团队成员权限细分,可分别控制是否可创建在根目录应用/知识库以及 API Key
|
||||||
|
2. 支持交互节点在嵌套工作流中使用。
|
||||||
|
3. 团队成员操作日志。
|
||||||
|
|
||||||
## ⚙️ 优化
|
## ⚙️ 优化
|
||||||
|
|
||||||
|
1. 繁体中文翻译。
|
||||||
|
|
||||||
|
|
||||||
## 🐛 修复
|
## 🐛 修复
|
||||||
|
|
||||||
1. password 检测规则错误
|
1. password 检测规则错误。
|
||||||
|
2. 分享链接无法隐藏知识库检索结果。
|
||||||
|
3. IOS 低版本正则兼容问题。
|
||||||
|
4. 修复问答提取队列错误后,计数器未清零问题,导致问答提取队列失效。
|
||||||
|
5. Debug 模式交互节点下一步可能造成死循环。
|
@@ -23,7 +23,7 @@ import { WorkflowResponseType } from '../../../../service/core/workflow/dispatch
|
|||||||
import { AiChatQuoteRoleType } from '../template/system/aiChat/type';
|
import { AiChatQuoteRoleType } from '../template/system/aiChat/type';
|
||||||
import { LafAccountType, OpenaiAccountType } from '../../../support/user/team/type';
|
import { LafAccountType, OpenaiAccountType } from '../../../support/user/team/type';
|
||||||
import { CompletionFinishReason } from '../../ai/type';
|
import { CompletionFinishReason } from '../../ai/type';
|
||||||
|
import { WorkflowInteractiveResponseType } from '../template/system/interactive/type';
|
||||||
export type ExternalProviderType = {
|
export type ExternalProviderType = {
|
||||||
openaiAccount?: OpenaiAccountType;
|
openaiAccount?: OpenaiAccountType;
|
||||||
externalWorkflowVariables?: Record<string, string>;
|
externalWorkflowVariables?: Record<string, string>;
|
||||||
@@ -55,6 +55,7 @@ export type ChatDispatchProps = {
|
|||||||
variables: Record<string, any>; // global variable
|
variables: Record<string, any>; // global variable
|
||||||
query: UserChatItemValueItemType[]; // trigger query
|
query: UserChatItemValueItemType[]; // trigger query
|
||||||
chatConfig: AppSchema['chatConfig'];
|
chatConfig: AppSchema['chatConfig'];
|
||||||
|
lastInteractive?: WorkflowInteractiveResponseType; // last interactive response
|
||||||
stream: boolean;
|
stream: boolean;
|
||||||
maxRunTimes: number;
|
maxRunTimes: number;
|
||||||
isToolCall?: boolean;
|
isToolCall?: boolean;
|
||||||
|
@@ -10,7 +10,19 @@ import { FlowNodeOutputItemType, ReferenceValueType } from '../type/io';
|
|||||||
import { ChatItemType, NodeOutputItemType } from '../../../core/chat/type';
|
import { ChatItemType, NodeOutputItemType } from '../../../core/chat/type';
|
||||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '../../../core/chat/constants';
|
import { ChatItemValueTypeEnum, ChatRoleEnum } from '../../../core/chat/constants';
|
||||||
import { replaceVariable, valToStr } from '../../../common/string/tools';
|
import { replaceVariable, valToStr } from '../../../common/string/tools';
|
||||||
|
import {
|
||||||
|
InteractiveNodeResponseType,
|
||||||
|
WorkflowInteractiveResponseType
|
||||||
|
} from '../template/system/interactive/type';
|
||||||
|
|
||||||
|
export const extractDeepestInteractive = (
|
||||||
|
interactive: WorkflowInteractiveResponseType
|
||||||
|
): WorkflowInteractiveResponseType => {
|
||||||
|
if (interactive?.type === 'childrenInteractive' && interactive.params?.childrenResponse) {
|
||||||
|
return extractDeepestInteractive(interactive.params.childrenResponse);
|
||||||
|
}
|
||||||
|
return interactive;
|
||||||
|
};
|
||||||
export const getMaxHistoryLimitFromNodes = (nodes: StoreNodeItemType[]): number => {
|
export const getMaxHistoryLimitFromNodes = (nodes: StoreNodeItemType[]): number => {
|
||||||
let limit = 10;
|
let limit = 10;
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
@@ -34,7 +46,9 @@ export const getMaxHistoryLimitFromNodes = (nodes: StoreNodeItemType[]): number
|
|||||||
1. Get the interactive data
|
1. Get the interactive data
|
||||||
2. Check that the workflow starts at the interaction node
|
2. Check that the workflow starts at the interaction node
|
||||||
*/
|
*/
|
||||||
export const getLastInteractiveValue = (histories: ChatItemType[]) => {
|
export const getLastInteractiveValue = (
|
||||||
|
histories: ChatItemType[]
|
||||||
|
): WorkflowInteractiveResponseType | undefined => {
|
||||||
const lastAIMessage = [...histories].reverse().find((item) => item.obj === ChatRoleEnum.AI);
|
const lastAIMessage = [...histories].reverse().find((item) => item.obj === ChatRoleEnum.AI);
|
||||||
|
|
||||||
if (lastAIMessage) {
|
if (lastAIMessage) {
|
||||||
@@ -45,7 +59,11 @@ export const getLastInteractiveValue = (histories: ChatItemType[]) => {
|
|||||||
lastValue.type !== ChatItemValueTypeEnum.interactive ||
|
lastValue.type !== ChatItemValueTypeEnum.interactive ||
|
||||||
!lastValue.interactive
|
!lastValue.interactive
|
||||||
) {
|
) {
|
||||||
return null;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastValue.interactive.type === 'childrenInteractive') {
|
||||||
|
return lastValue.interactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check is user select
|
// Check is user select
|
||||||
@@ -62,38 +80,29 @@ export const getLastInteractiveValue = (histories: ChatItemType[]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initWorkflowEdgeStatus = (
|
export const initWorkflowEdgeStatus = (
|
||||||
edges: StoreEdgeItemType[] | RuntimeEdgeItemType[],
|
edges: StoreEdgeItemType[],
|
||||||
histories?: ChatItemType[]
|
lastInteractive?: WorkflowInteractiveResponseType
|
||||||
): RuntimeEdgeItemType[] => {
|
): RuntimeEdgeItemType[] => {
|
||||||
// If there is a history, use the last interactive value
|
if (lastInteractive) {
|
||||||
if (histories && histories.length > 0) {
|
const memoryEdges = lastInteractive.memoryEdges || [];
|
||||||
const memoryEdges = getLastInteractiveValue(histories)?.memoryEdges;
|
|
||||||
|
|
||||||
if (memoryEdges && memoryEdges.length > 0) {
|
if (memoryEdges && memoryEdges.length > 0) {
|
||||||
return memoryEdges;
|
return memoryEdges;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return edges?.map((edge) => ({ ...edge, status: 'waiting' })) || [];
|
||||||
edges?.map((edge) => ({
|
|
||||||
...edge,
|
|
||||||
status: 'waiting'
|
|
||||||
})) || []
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getWorkflowEntryNodeIds = (
|
export const getWorkflowEntryNodeIds = (
|
||||||
nodes: (StoreNodeItemType | RuntimeNodeItemType)[],
|
nodes: (StoreNodeItemType | RuntimeNodeItemType)[],
|
||||||
histories?: ChatItemType[]
|
lastInteractive?: WorkflowInteractiveResponseType
|
||||||
) => {
|
) => {
|
||||||
// If there is a history, use the last interactive entry node
|
if (lastInteractive) {
|
||||||
if (histories && histories.length > 0) {
|
const entryNodeIds = lastInteractive.entryNodeIds || [];
|
||||||
const entryNodeIds = getLastInteractiveValue(histories)?.entryNodeIds;
|
|
||||||
|
|
||||||
if (Array.isArray(entryNodeIds) && entryNodeIds.length > 0) {
|
if (Array.isArray(entryNodeIds) && entryNodeIds.length > 0) {
|
||||||
return entryNodeIds;
|
return entryNodeIds;
|
||||||
}
|
}
|
||||||
@@ -396,10 +405,10 @@ export const textAdaptGptResponse = ({
|
|||||||
|
|
||||||
/* Update runtimeNode's outputs with interactive data from history */
|
/* Update runtimeNode's outputs with interactive data from history */
|
||||||
export function rewriteNodeOutputByHistories(
|
export function rewriteNodeOutputByHistories(
|
||||||
histories: ChatItemType[],
|
runtimeNodes: RuntimeNodeItemType[],
|
||||||
runtimeNodes: RuntimeNodeItemType[]
|
lastInteractive?: InteractiveNodeResponseType
|
||||||
) {
|
) {
|
||||||
const interactive = getLastInteractiveValue(histories);
|
const interactive = lastInteractive;
|
||||||
if (!interactive?.nodeOutputs) {
|
if (!interactive?.nodeOutputs) {
|
||||||
return runtimeNodes;
|
return runtimeNodes;
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import type { NodeOutputItemType } from '../../../../chat/type';
|
import type { NodeOutputItemType } from '../../../../chat/type';
|
||||||
import type { FlowNodeOutputItemType } from '../../../type/io';
|
import type { FlowNodeOutputItemType } from '../../../type/io';
|
||||||
import type { RuntimeEdgeItemType } from '../../../runtime/type';
|
|
||||||
import { FlowNodeInputTypeEnum } from 'core/workflow/node/constant';
|
import { FlowNodeInputTypeEnum } from 'core/workflow/node/constant';
|
||||||
import { WorkflowIOValueTypeEnum } from 'core/workflow/constants';
|
import { WorkflowIOValueTypeEnum } from 'core/workflow/constants';
|
||||||
import type { ChatCompletionMessageParam } from '../../../../ai/type';
|
import type { ChatCompletionMessageParam } from '../../../../ai/type';
|
||||||
@@ -9,7 +8,6 @@ type InteractiveBasicType = {
|
|||||||
entryNodeIds: string[];
|
entryNodeIds: string[];
|
||||||
memoryEdges: RuntimeEdgeItemType[];
|
memoryEdges: RuntimeEdgeItemType[];
|
||||||
nodeOutputs: NodeOutputItemType[];
|
nodeOutputs: NodeOutputItemType[];
|
||||||
|
|
||||||
toolParams?: {
|
toolParams?: {
|
||||||
entryNodeIds: string[]; // 记录工具中,交互节点的 Id,而不是起始工作流的入口
|
entryNodeIds: string[]; // 记录工具中,交互节点的 Id,而不是起始工作流的入口
|
||||||
memoryMessages: ChatCompletionMessageParam[]; // 这轮工具中,产生的新的 messages
|
memoryMessages: ChatCompletionMessageParam[]; // 这轮工具中,产生的新的 messages
|
||||||
@@ -23,6 +21,13 @@ type InteractiveNodeType = {
|
|||||||
nodeOutputs?: NodeOutputItemType[];
|
nodeOutputs?: NodeOutputItemType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ChildrenInteractive = InteractiveNodeType & {
|
||||||
|
type: 'childrenInteractive';
|
||||||
|
params: {
|
||||||
|
childrenResponse?: WorkflowInteractiveResponseType;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type UserSelectOptionItemType = {
|
export type UserSelectOptionItemType = {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
@@ -62,5 +67,9 @@ type UserInputInteractive = InteractiveNodeType & {
|
|||||||
submitted?: boolean;
|
submitted?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type InteractiveNodeResponseType = UserSelectInteractive | UserInputInteractive;
|
|
||||||
|
export type InteractiveNodeResponseType =
|
||||||
|
| UserSelectInteractive
|
||||||
|
| UserInputInteractive
|
||||||
|
| ChildrenInteractive;
|
||||||
export type WorkflowInteractiveResponseType = InteractiveBasicType & InteractiveNodeResponseType;
|
export type WorkflowInteractiveResponseType = InteractiveBasicType & InteractiveNodeResponseType;
|
||||||
|
14
packages/global/support/operationLog/constants.ts
Normal file
14
packages/global/support/operationLog/constants.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export enum OperationLogEventEnum {
|
||||||
|
LOGIN = 'LOGIN',
|
||||||
|
CREATE_INVITATION_LINK = 'CREATE_INVITATION_LINK',
|
||||||
|
JOIN_TEAM = 'JOIN_TEAM',
|
||||||
|
CHANGE_MEMBER_NAME = 'CHANGE_MEMBER_NAME',
|
||||||
|
KICK_OUT_TEAM = 'KICK_OUT_TEAM',
|
||||||
|
CREATE_DEPARTMENT = 'CREATE_DEPARTMENT',
|
||||||
|
CHANGE_DEPARTMENT = 'CHANGE_DEPARTMENT',
|
||||||
|
DELETE_DEPARTMENT = 'DELETE_DEPARTMENT',
|
||||||
|
RELOCATE_DEPARTMENT = 'RELOCATE_DEPARTMENT',
|
||||||
|
CREATE_GROUP = 'CREATE_GROUP',
|
||||||
|
DELETE_GROUP = 'DELETE_GROUP',
|
||||||
|
ASSIGN_PERMISSION = 'ASSIGN_PERMISSION'
|
||||||
|
}
|
19
packages/global/support/operationLog/type.d.ts
vendored
Normal file
19
packages/global/support/operationLog/type.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { SourceMemberType } from '../user/type';
|
||||||
|
import { OperationLogEventEnum } from './constants';
|
||||||
|
|
||||||
|
export type OperationLogSchema = {
|
||||||
|
_id: string;
|
||||||
|
tmbId: string;
|
||||||
|
teamId: string;
|
||||||
|
timestamp: Date;
|
||||||
|
event: `${OperationLogEventEnum}`;
|
||||||
|
metadata?: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OperationListItemType = {
|
||||||
|
_id: string;
|
||||||
|
sourceMember: SourceMemberType;
|
||||||
|
event: `${OperationLogEventEnum}`;
|
||||||
|
timestamp: Date;
|
||||||
|
metadata: Record<string, string>;
|
||||||
|
};
|
@@ -1,6 +1,8 @@
|
|||||||
import { PerConstructPros, Permission } from '../controller';
|
import { PerConstructPros, Permission } from '../controller';
|
||||||
import {
|
import {
|
||||||
|
TeamApikeyCreatePermissionVal,
|
||||||
TeamAppCreatePermissionVal,
|
TeamAppCreatePermissionVal,
|
||||||
|
TeamDatasetCreatePermissionVal,
|
||||||
TeamDefaultPermissionVal,
|
TeamDefaultPermissionVal,
|
||||||
TeamPermissionList
|
TeamPermissionList
|
||||||
} from './constant';
|
} from './constant';
|
||||||
@@ -23,8 +25,8 @@ export class TeamPermission extends Permission {
|
|||||||
|
|
||||||
this.setUpdatePermissionCallback(() => {
|
this.setUpdatePermissionCallback(() => {
|
||||||
this.hasAppCreatePer = this.checkPer(TeamAppCreatePermissionVal);
|
this.hasAppCreatePer = this.checkPer(TeamAppCreatePermissionVal);
|
||||||
this.hasDatasetCreatePer = this.checkPer(TeamAppCreatePermissionVal);
|
this.hasDatasetCreatePer = this.checkPer(TeamDatasetCreatePermissionVal);
|
||||||
this.hasApikeyCreatePer = this.checkPer(TeamAppCreatePermissionVal);
|
this.hasApikeyCreatePer = this.checkPer(TeamApikeyCreatePermissionVal);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,7 @@ import { mergeChatResponseData } from '@fastgpt/global/core/chat/utils';
|
|||||||
import { pushChatLog } from './pushChatLog';
|
import { pushChatLog } from './pushChatLog';
|
||||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
|
import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@@ -209,34 +210,24 @@ export const updateInteractiveChat = async ({
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (interactiveValue.interactive.type === 'userSelect') {
|
let finalInteractive = extractDeepestInteractive(interactiveValue.interactive);
|
||||||
interactiveValue.interactive = {
|
|
||||||
...interactiveValue.interactive,
|
if (finalInteractive.type === 'userSelect') {
|
||||||
params: {
|
finalInteractive.params.userSelectedVal = userInteractiveVal;
|
||||||
...interactiveValue.interactive.params,
|
|
||||||
userSelectedVal: userInteractiveVal
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else if (
|
} else if (
|
||||||
interactiveValue.interactive.type === 'userInput' &&
|
finalInteractive.type === 'userInput' &&
|
||||||
typeof parsedUserInteractiveVal === 'object'
|
typeof parsedUserInteractiveVal === 'object'
|
||||||
) {
|
) {
|
||||||
interactiveValue.interactive = {
|
finalInteractive.params.inputForm = finalInteractive.params.inputForm.map((item) => {
|
||||||
...interactiveValue.interactive,
|
const itemValue = parsedUserInteractiveVal[item.label];
|
||||||
params: {
|
return itemValue !== undefined
|
||||||
...interactiveValue.interactive.params,
|
? {
|
||||||
inputForm: interactiveValue.interactive.params.inputForm.map((item) => {
|
...item,
|
||||||
const itemValue = parsedUserInteractiveVal[item.label];
|
value: itemValue
|
||||||
return itemValue !== undefined
|
}
|
||||||
? {
|
: item;
|
||||||
...item,
|
});
|
||||||
value: itemValue
|
finalInteractive.params.submitted = true;
|
||||||
}
|
|
||||||
: item;
|
|
||||||
}),
|
|
||||||
submitted: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aiResponse.customFeedbacks) {
|
if (aiResponse.customFeedbacks) {
|
||||||
|
@@ -141,6 +141,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
} else {
|
} else {
|
||||||
props.workflowDispatchDeep += 1;
|
props.workflowDispatchDeep += 1;
|
||||||
}
|
}
|
||||||
|
const isRootRuntime = props.workflowDispatchDeep === 1;
|
||||||
|
|
||||||
if (props.workflowDispatchDeep > 20) {
|
if (props.workflowDispatchDeep > 20) {
|
||||||
return {
|
return {
|
||||||
@@ -161,25 +162,28 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
let workflowRunTimes = 0;
|
let workflowRunTimes = 0;
|
||||||
|
|
||||||
// set sse response headers
|
// set sse response headers
|
||||||
if (stream && res) {
|
if (isRootRuntime) {
|
||||||
res.setHeader('Content-Type', 'text/event-stream;charset=utf-8');
|
res?.setHeader('Connection', 'keep-alive'); // Set keepalive for long connection
|
||||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
if (stream && res) {
|
||||||
res.setHeader('X-Accel-Buffering', 'no');
|
res.setHeader('Content-Type', 'text/event-stream;charset=utf-8');
|
||||||
res.setHeader('Cache-Control', 'no-cache, no-transform');
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||||
|
res.setHeader('X-Accel-Buffering', 'no');
|
||||||
|
res.setHeader('Cache-Control', 'no-cache, no-transform');
|
||||||
|
|
||||||
// 10s sends a message to prevent the browser from thinking that the connection is disconnected
|
// 10s sends a message to prevent the browser from thinking that the connection is disconnected
|
||||||
const sendStreamTimerSign = () => {
|
const sendStreamTimerSign = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
props?.workflowStreamResponse?.({
|
props?.workflowStreamResponse?.({
|
||||||
event: SseResponseEventEnum.answer,
|
event: SseResponseEventEnum.answer,
|
||||||
data: textAdaptGptResponse({
|
data: textAdaptGptResponse({
|
||||||
text: ''
|
text: ''
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
sendStreamTimerSign();
|
sendStreamTimerSign();
|
||||||
}, 10000);
|
}, 10000);
|
||||||
};
|
};
|
||||||
sendStreamTimerSign();
|
sendStreamTimerSign();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
variables = {
|
variables = {
|
||||||
@@ -325,10 +329,9 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (props.mode === 'debug') {
|
if (props.mode === 'debug') {
|
||||||
debugNextStepRunNodes = debugNextStepRunNodes.concat([
|
debugNextStepRunNodes = debugNextStepRunNodes.concat(
|
||||||
...nextStepActiveNodes,
|
props.lastInteractive ? nextStepActiveNodes : [...nextStepActiveNodes, ...nextStepSkipNodes]
|
||||||
...nextStepSkipNodes
|
);
|
||||||
]);
|
|
||||||
return {
|
return {
|
||||||
nextStepActiveNodes: [],
|
nextStepActiveNodes: [],
|
||||||
nextStepSkipNodes: []
|
nextStepSkipNodes: []
|
||||||
@@ -374,7 +377,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Tool call, not need interactive response
|
// Tool call, not need interactive response
|
||||||
if (!props.isToolCall) {
|
if (!props.isToolCall && isRootRuntime) {
|
||||||
props.workflowStreamResponse?.({
|
props.workflowStreamResponse?.({
|
||||||
event: SseResponseEventEnum.interactive,
|
event: SseResponseEventEnum.interactive,
|
||||||
data: { interactive: interactiveResult }
|
data: { interactive: interactiveResult }
|
||||||
@@ -428,14 +431,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
if (!nodeRunResult) return [];
|
if (!nodeRunResult) return [];
|
||||||
if (res?.closed) {
|
|
||||||
addLog.warn('Request is closed', {
|
|
||||||
appId: props.runningAppInfo.id,
|
|
||||||
nodeId: node.nodeId,
|
|
||||||
nodeName: node.name
|
|
||||||
});
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
特殊情况:
|
特殊情况:
|
||||||
@@ -492,6 +487,15 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
await Promise.all(nextStepSkipNodes.map((node) => checkNodeCanRun(node, skippedNodeIdList)))
|
await Promise.all(nextStepSkipNodes.map((node) => checkNodeCanRun(node, skippedNodeIdList)))
|
||||||
).flat();
|
).flat();
|
||||||
|
|
||||||
|
if (res?.closed) {
|
||||||
|
addLog.warn('Request is closed', {
|
||||||
|
appId: props.runningAppInfo.id,
|
||||||
|
nodeId: node.nodeId,
|
||||||
|
nodeName: node.name
|
||||||
|
});
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...nextStepActiveNodes,
|
...nextStepActiveNodes,
|
||||||
...nextStepSkipNodes,
|
...nextStepSkipNodes,
|
||||||
@@ -632,7 +636,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
if (
|
if (
|
||||||
version === 'v2' &&
|
version === 'v2' &&
|
||||||
!props.isToolCall &&
|
!props.isToolCall &&
|
||||||
!props.runningAppInfo.isChildApp &&
|
isRootRuntime &&
|
||||||
formatResponseData &&
|
formatResponseData &&
|
||||||
!(!props.responseDetail && filterModuleTypeList.includes(formatResponseData.moduleType))
|
!(!props.responseDetail && filterModuleTypeList.includes(formatResponseData.moduleType))
|
||||||
) {
|
) {
|
||||||
@@ -721,7 +725,9 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
|||||||
entryNodeIds: nodeInteractiveResponse.entryNodeIds,
|
entryNodeIds: nodeInteractiveResponse.entryNodeIds,
|
||||||
interactiveResponse: nodeInteractiveResponse.interactiveResponse
|
interactiveResponse: nodeInteractiveResponse.interactiveResponse
|
||||||
});
|
});
|
||||||
chatAssistantResponse.push(interactiveAssistant);
|
if (isRootRuntime) {
|
||||||
|
chatAssistantResponse.push(interactiveAssistant);
|
||||||
|
}
|
||||||
return interactiveAssistant.interactive;
|
return interactiveAssistant.interactive;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@@ -10,7 +10,6 @@ import type {
|
|||||||
UserInputInteractive
|
UserInputInteractive
|
||||||
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||||
import { addLog } from '../../../../common/system/log';
|
import { addLog } from '../../../../common/system/log';
|
||||||
import { getLastInteractiveValue } from '@fastgpt/global/core/workflow/runtime/utils';
|
|
||||||
|
|
||||||
type Props = ModuleDispatchProps<{
|
type Props = ModuleDispatchProps<{
|
||||||
[NodeInputKeyEnum.description]: string;
|
[NodeInputKeyEnum.description]: string;
|
||||||
@@ -29,13 +28,13 @@ export const dispatchFormInput = async (props: Props): Promise<FormInputResponse
|
|||||||
histories,
|
histories,
|
||||||
node,
|
node,
|
||||||
params: { description, userInputForms },
|
params: { description, userInputForms },
|
||||||
query
|
query,
|
||||||
|
lastInteractive
|
||||||
} = props;
|
} = props;
|
||||||
const { isEntry } = node;
|
const { isEntry } = node;
|
||||||
const interactive = getLastInteractiveValue(histories);
|
|
||||||
|
|
||||||
// Interactive node is not the entry node, return interactive result
|
// Interactive node is not the entry node, return interactive result
|
||||||
if (!isEntry || interactive?.type !== 'userInput') {
|
if (!isEntry || lastInteractive?.type !== 'userInput') {
|
||||||
return {
|
return {
|
||||||
[DispatchNodeResponseKeyEnum.interactive]: {
|
[DispatchNodeResponseKeyEnum.interactive]: {
|
||||||
type: 'userInput',
|
type: 'userInput',
|
||||||
|
@@ -10,7 +10,6 @@ import type {
|
|||||||
UserSelectOptionItemType
|
UserSelectOptionItemType
|
||||||
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||||
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
||||||
import { getLastInteractiveValue } from '@fastgpt/global/core/workflow/runtime/utils';
|
|
||||||
|
|
||||||
type Props = ModuleDispatchProps<{
|
type Props = ModuleDispatchProps<{
|
||||||
[NodeInputKeyEnum.description]: string;
|
[NodeInputKeyEnum.description]: string;
|
||||||
@@ -27,13 +26,13 @@ export const dispatchUserSelect = async (props: Props): Promise<UserSelectRespon
|
|||||||
histories,
|
histories,
|
||||||
node,
|
node,
|
||||||
params: { description, userSelectOptions },
|
params: { description, userSelectOptions },
|
||||||
query
|
query,
|
||||||
|
lastInteractive
|
||||||
} = props;
|
} = props;
|
||||||
const { nodeId, isEntry } = node;
|
const { nodeId, isEntry } = node;
|
||||||
const interactive = getLastInteractiveValue(histories);
|
|
||||||
|
|
||||||
// Interactive node is not the entry node, return interactive result
|
// Interactive node is not the entry node, return interactive result
|
||||||
if (!isEntry || interactive?.type !== 'userSelect') {
|
if (!isEntry || lastInteractive?.type !== 'userSelect') {
|
||||||
return {
|
return {
|
||||||
[DispatchNodeResponseKeyEnum.interactive]: {
|
[DispatchNodeResponseKeyEnum.interactive]: {
|
||||||
type: 'userSelect',
|
type: 'userSelect',
|
||||||
|
@@ -18,6 +18,7 @@ import { authAppByTmbId } from '../../../../support/permission/app/auth';
|
|||||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
import { getAppVersionById } from '../../../app/version/controller';
|
import { getAppVersionById } from '../../../app/version/controller';
|
||||||
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
||||||
|
import { ChildrenInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||||
|
|
||||||
type Props = ModuleDispatchProps<{
|
type Props = ModuleDispatchProps<{
|
||||||
[NodeInputKeyEnum.userChatInput]: string;
|
[NodeInputKeyEnum.userChatInput]: string;
|
||||||
@@ -27,6 +28,7 @@ type Props = ModuleDispatchProps<{
|
|||||||
[NodeInputKeyEnum.fileUrlList]?: string[];
|
[NodeInputKeyEnum.fileUrlList]?: string[];
|
||||||
}>;
|
}>;
|
||||||
type Response = DispatchNodeResultType<{
|
type Response = DispatchNodeResultType<{
|
||||||
|
[DispatchNodeResponseKeyEnum.interactive]?: ChildrenInteractive;
|
||||||
[NodeOutputKeyEnum.answerText]: string;
|
[NodeOutputKeyEnum.answerText]: string;
|
||||||
[NodeOutputKeyEnum.history]: ChatItemType[];
|
[NodeOutputKeyEnum.history]: ChatItemType[];
|
||||||
}>;
|
}>;
|
||||||
@@ -36,6 +38,7 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
|
|||||||
runningAppInfo,
|
runningAppInfo,
|
||||||
histories,
|
histories,
|
||||||
query,
|
query,
|
||||||
|
lastInteractive,
|
||||||
node: { pluginId: appId, version },
|
node: { pluginId: appId, version },
|
||||||
workflowStreamResponse,
|
workflowStreamResponse,
|
||||||
params,
|
params,
|
||||||
@@ -100,31 +103,41 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
|
|||||||
appId: String(appData._id)
|
appId: String(appData._id)
|
||||||
};
|
};
|
||||||
|
|
||||||
const { flowResponses, flowUsages, assistantResponses, runTimes } = await dispatchWorkFlow({
|
const childrenInteractive =
|
||||||
...props,
|
lastInteractive?.type === 'childrenInteractive'
|
||||||
// Rewrite stream mode
|
? lastInteractive.params.childrenResponse
|
||||||
...(system_forbid_stream
|
: undefined;
|
||||||
? {
|
const entryNodeIds = getWorkflowEntryNodeIds(nodes, childrenInteractive || undefined);
|
||||||
stream: false,
|
const runtimeNodes = storeNodes2RuntimeNodes(nodes, entryNodeIds);
|
||||||
workflowStreamResponse: undefined
|
const runtimeEdges = initWorkflowEdgeStatus(edges, childrenInteractive);
|
||||||
}
|
const theQuery = childrenInteractive
|
||||||
: {}),
|
? query
|
||||||
runningAppInfo: {
|
: runtimePrompt2ChatsValue({ files: userInputFiles, text: userChatInput });
|
||||||
id: String(appData._id),
|
|
||||||
teamId: String(appData.teamId),
|
const { flowResponses, flowUsages, assistantResponses, runTimes, workflowInteractiveResponse } =
|
||||||
tmbId: String(appData.tmbId),
|
await dispatchWorkFlow({
|
||||||
isChildApp: true
|
...props,
|
||||||
},
|
lastInteractive: childrenInteractive,
|
||||||
runtimeNodes: storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes)),
|
// Rewrite stream mode
|
||||||
runtimeEdges: initWorkflowEdgeStatus(edges),
|
...(system_forbid_stream
|
||||||
histories: chatHistories,
|
? {
|
||||||
variables: childrenRunVariables,
|
stream: false,
|
||||||
query: runtimePrompt2ChatsValue({
|
workflowStreamResponse: undefined
|
||||||
files: userInputFiles,
|
}
|
||||||
text: userChatInput
|
: {}),
|
||||||
}),
|
runningAppInfo: {
|
||||||
chatConfig
|
id: String(appData._id),
|
||||||
});
|
teamId: String(appData.teamId),
|
||||||
|
tmbId: String(appData.tmbId),
|
||||||
|
isChildApp: true
|
||||||
|
},
|
||||||
|
runtimeNodes,
|
||||||
|
runtimeEdges,
|
||||||
|
histories: chatHistories,
|
||||||
|
variables: childrenRunVariables,
|
||||||
|
query: theQuery,
|
||||||
|
chatConfig
|
||||||
|
});
|
||||||
|
|
||||||
const completeMessages = chatHistories.concat([
|
const completeMessages = chatHistories.concat([
|
||||||
{
|
{
|
||||||
@@ -142,6 +155,14 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
|
|||||||
const usagePoints = flowUsages.reduce((sum, item) => sum + (item.totalPoints || 0), 0);
|
const usagePoints = flowUsages.reduce((sum, item) => sum + (item.totalPoints || 0), 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
[DispatchNodeResponseKeyEnum.interactive]: workflowInteractiveResponse
|
||||||
|
? {
|
||||||
|
type: 'childrenInteractive',
|
||||||
|
params: {
|
||||||
|
childrenResponse: workflowInteractiveResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
assistantResponses: system_forbid_stream ? [] : assistantResponses,
|
assistantResponses: system_forbid_stream ? [] : assistantResponses,
|
||||||
[DispatchNodeResponseKeyEnum.runTimes]: runTimes,
|
[DispatchNodeResponseKeyEnum.runTimes]: runTimes,
|
||||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||||
|
26
packages/service/support/operationLog/addOperationLog.ts
Normal file
26
packages/service/support/operationLog/addOperationLog.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { MongoOperationLog } from './schema';
|
||||||
|
import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants';
|
||||||
|
import { TemplateParamsMap } from './constants';
|
||||||
|
import { retryFn } from '../../../global/common/system/utils';
|
||||||
|
|
||||||
|
export function addOperationLog<T extends OperationLogEventEnum>({
|
||||||
|
teamId,
|
||||||
|
tmbId,
|
||||||
|
event,
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
tmbId: string;
|
||||||
|
teamId: string;
|
||||||
|
event: T;
|
||||||
|
params?: TemplateParamsMap[T];
|
||||||
|
}) {
|
||||||
|
console.log('Insert log');
|
||||||
|
retryFn(() =>
|
||||||
|
MongoOperationLog.create({
|
||||||
|
tmbId: tmbId,
|
||||||
|
teamId: teamId,
|
||||||
|
event,
|
||||||
|
metadata: params
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
85
packages/service/support/operationLog/constants.ts
Normal file
85
packages/service/support/operationLog/constants.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants';
|
||||||
|
import { i18nT } from '../../../web/i18n/utils';
|
||||||
|
|
||||||
|
export const operationLogI18nMap = {
|
||||||
|
[OperationLogEventEnum.LOGIN]: {
|
||||||
|
content: i18nT('account_team:log_login'),
|
||||||
|
typeLabel: i18nT('account_team:login')
|
||||||
|
},
|
||||||
|
[OperationLogEventEnum.CREATE_INVITATION_LINK]: {
|
||||||
|
content: i18nT('account_team:log_create_invitation_link'),
|
||||||
|
typeLabel: i18nT('account_team:create_invitation_link')
|
||||||
|
},
|
||||||
|
[OperationLogEventEnum.JOIN_TEAM]: {
|
||||||
|
content: i18nT('account_team:log_join_team'),
|
||||||
|
typeLabel: i18nT('account_team:join_team')
|
||||||
|
},
|
||||||
|
[OperationLogEventEnum.CHANGE_MEMBER_NAME]: {
|
||||||
|
content: i18nT('account_team:log_change_member_name'),
|
||||||
|
typeLabel: i18nT('account_team:change_member_name')
|
||||||
|
},
|
||||||
|
[OperationLogEventEnum.KICK_OUT_TEAM]: {
|
||||||
|
content: i18nT('account_team:log_kick_out_team'),
|
||||||
|
typeLabel: i18nT('account_team:kick_out_team')
|
||||||
|
},
|
||||||
|
[OperationLogEventEnum.CREATE_DEPARTMENT]: {
|
||||||
|
content: i18nT('account_team:log_create_department'),
|
||||||
|
typeLabel: i18nT('account_team:create_department')
|
||||||
|
},
|
||||||
|
[OperationLogEventEnum.CHANGE_DEPARTMENT]: {
|
||||||
|
content: i18nT('account_team:log_change_department'),
|
||||||
|
typeLabel: i18nT('account_team:change_department_name')
|
||||||
|
},
|
||||||
|
[OperationLogEventEnum.DELETE_DEPARTMENT]: {
|
||||||
|
content: i18nT('account_team:log_delete_department'),
|
||||||
|
typeLabel: i18nT('account_team:delete_department')
|
||||||
|
},
|
||||||
|
[OperationLogEventEnum.RELOCATE_DEPARTMENT]: {
|
||||||
|
content: i18nT('account_team:log_relocate_department'),
|
||||||
|
typeLabel: i18nT('account_team:relocate_department')
|
||||||
|
},
|
||||||
|
[OperationLogEventEnum.CREATE_GROUP]: {
|
||||||
|
content: i18nT('account_team:log_create_group'),
|
||||||
|
typeLabel: i18nT('account_team:create_group')
|
||||||
|
},
|
||||||
|
[OperationLogEventEnum.DELETE_GROUP]: {
|
||||||
|
content: i18nT('account_team:log_delete_group'),
|
||||||
|
typeLabel: i18nT('account_team:delete_group')
|
||||||
|
},
|
||||||
|
[OperationLogEventEnum.ASSIGN_PERMISSION]: {
|
||||||
|
content: i18nT('account_team:log_assign_permission'),
|
||||||
|
typeLabel: i18nT('account_team:assign_permission')
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type TemplateParamsMap = {
|
||||||
|
[OperationLogEventEnum.LOGIN]: { name?: string };
|
||||||
|
[OperationLogEventEnum.CREATE_INVITATION_LINK]: { name?: string; link: string };
|
||||||
|
[OperationLogEventEnum.JOIN_TEAM]: { name?: string; link: string };
|
||||||
|
[OperationLogEventEnum.CHANGE_MEMBER_NAME]: {
|
||||||
|
name?: string;
|
||||||
|
memberName: string;
|
||||||
|
newName: string;
|
||||||
|
};
|
||||||
|
[OperationLogEventEnum.KICK_OUT_TEAM]: {
|
||||||
|
name?: string;
|
||||||
|
memberName: string;
|
||||||
|
};
|
||||||
|
[OperationLogEventEnum.CREATE_DEPARTMENT]: { name?: string; departmentName: string };
|
||||||
|
[OperationLogEventEnum.CHANGE_DEPARTMENT]: {
|
||||||
|
name?: string;
|
||||||
|
departmentName: string;
|
||||||
|
};
|
||||||
|
[OperationLogEventEnum.DELETE_DEPARTMENT]: { name?: string; departmentName: string };
|
||||||
|
[OperationLogEventEnum.RELOCATE_DEPARTMENT]: {
|
||||||
|
name?: string;
|
||||||
|
departmentName: string;
|
||||||
|
};
|
||||||
|
[OperationLogEventEnum.CREATE_GROUP]: { name?: string; groupName: string };
|
||||||
|
[OperationLogEventEnum.DELETE_GROUP]: { name?: string; groupName: string };
|
||||||
|
[OperationLogEventEnum.ASSIGN_PERMISSION]: {
|
||||||
|
name?: string;
|
||||||
|
objectName: string;
|
||||||
|
permission: string;
|
||||||
|
};
|
||||||
|
};
|
40
packages/service/support/operationLog/schema.ts
Normal file
40
packages/service/support/operationLog/schema.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { Schema, getMongoLogModel } from '../../common/mongo';
|
||||||
|
import type { OperationLogSchema } from '@fastgpt/global/support/operationLog/type';
|
||||||
|
import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants';
|
||||||
|
import {
|
||||||
|
TeamCollectionName,
|
||||||
|
TeamMemberCollectionName
|
||||||
|
} from '@fastgpt/global/support/user/team/constant';
|
||||||
|
|
||||||
|
export const OperationLogCollectionName = 'operationLog';
|
||||||
|
|
||||||
|
const OperationLogSchema = new Schema({
|
||||||
|
tmbId: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: TeamMemberCollectionName,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
teamId: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: TeamCollectionName,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
timestamp: {
|
||||||
|
type: Date,
|
||||||
|
default: () => new Date()
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
type: String,
|
||||||
|
enum: Object.values(OperationLogEventEnum),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
type: Object,
|
||||||
|
default: {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const MongoOperationLog = getMongoLogModel<OperationLogSchema>(
|
||||||
|
OperationLogCollectionName,
|
||||||
|
OperationLogSchema
|
||||||
|
);
|
@@ -104,8 +104,11 @@ export async function addSourceMember<T extends { tmbId: string }>({
|
|||||||
const tmb = tmbList.find((tmb) => String(tmb._id) === String(item.tmbId));
|
const tmb = tmbList.find((tmb) => String(tmb._id) === String(item.tmbId));
|
||||||
if (!tmb) return;
|
if (!tmb) return;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const formatItem = typeof item.toObject === 'function' ? item.toObject() : item;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...item,
|
...formatItem,
|
||||||
sourceMember: { name: tmb.name, avatar: tmb.avatar, status: tmb.status }
|
sourceMember: { name: tmb.name, avatar: tmb.avatar, status: tmb.status }
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
@@ -5,17 +5,23 @@
|
|||||||
"7days": "7 Days",
|
"7days": "7 Days",
|
||||||
"accept": "accept",
|
"accept": "accept",
|
||||||
"action": "operate",
|
"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_group": "Confirm to delete group?",
|
||||||
"confirm_delete_member": "Confirm to delete member?",
|
"confirm_delete_member": "Confirm to delete member?",
|
||||||
"confirm_delete_org": "Confirm to delete organization?",
|
"confirm_delete_org": "Confirm to delete organization?",
|
||||||
"confirm_forbidden": "Confirm forbidden",
|
"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.",
|
"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",
|
"copy_link": "Copy link",
|
||||||
|
"create_department": "Create a sub-department",
|
||||||
"create_group": "Create group",
|
"create_group": "Create group",
|
||||||
"create_invitation_link": "Create Invitation Link",
|
"create_invitation_link": "Create Invitation Link",
|
||||||
"create_org": "Create organization",
|
"create_org": "Create organization",
|
||||||
"create_sub_org": "Create sub-organization",
|
"create_sub_org": "Create sub-organization",
|
||||||
"delete": "delete",
|
"delete": "delete",
|
||||||
|
"delete_department": "Delete sub-department",
|
||||||
|
"delete_group": "Delete a group",
|
||||||
"delete_org": "Delete organization",
|
"delete_org": "Delete organization",
|
||||||
"edit_info": "Edit information",
|
"edit_info": "Edit information",
|
||||||
"edit_member": "Edit user",
|
"edit_member": "Edit user",
|
||||||
@@ -37,21 +43,51 @@
|
|||||||
"invitation_link_list": "Invitation link list",
|
"invitation_link_list": "Invitation link list",
|
||||||
"invite_member": "Invite members",
|
"invite_member": "Invite members",
|
||||||
"invited": "Invited",
|
"invited": "Invited",
|
||||||
|
"join_team": "Join the team",
|
||||||
|
"kick_out_team": "Remove members",
|
||||||
"label_sync": "Tag sync",
|
"label_sync": "Tag sync",
|
||||||
"leave_team_failed": "Leaving the team exception",
|
"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",
|
"manage_member": "Managing members",
|
||||||
"member": "member",
|
"member": "member",
|
||||||
"member_group": "Belonging to member group",
|
"member_group": "Belonging to member group",
|
||||||
"move_member": "Move member",
|
"move_member": "Move member",
|
||||||
"move_org": "Move organization",
|
"move_org": "Move organization",
|
||||||
|
"operation_log": "log",
|
||||||
"org": "organization",
|
"org": "organization",
|
||||||
"org_description": "Organization description",
|
"org_description": "Organization description",
|
||||||
"org_name": "Organization name",
|
"org_name": "Organization name",
|
||||||
"owner": "owner",
|
"owner": "owner",
|
||||||
"permission": "Permissions",
|
"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",
|
"remark": "remark",
|
||||||
"remove_tip": "Confirm to remove {{username}} from the team?",
|
"remove_tip": "Confirm to remove {{username}} from the team?",
|
||||||
"retain_admin_permissions": "Keep administrator rights",
|
"retain_admin_permissions": "Keep administrator rights",
|
||||||
|
"search_log": "Search log",
|
||||||
"search_member_group_name": "Search member/group name",
|
"search_member_group_name": "Search member/group name",
|
||||||
"total_team_members": "{{amount}} members in total",
|
"total_team_members": "{{amount}} members in total",
|
||||||
"transfer_ownership": "transfer owner",
|
"transfer_ownership": "transfer owner",
|
||||||
@@ -61,13 +97,5 @@
|
|||||||
"user_team_invite_member": "Invite members",
|
"user_team_invite_member": "Invite members",
|
||||||
"user_team_leave_team": "Leave the team",
|
"user_team_leave_team": "Leave the team",
|
||||||
"user_team_leave_team_failed": "Failure to leave the team",
|
"user_team_leave_team_failed": "Failure to leave the team",
|
||||||
"waiting": "To be accepted",
|
"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"
|
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,9 @@
|
|||||||
"7days": "7天",
|
"7days": "7天",
|
||||||
"accept": "接受",
|
"accept": "接受",
|
||||||
"action": "操作",
|
"action": "操作",
|
||||||
|
"assign_permission": "权限变更",
|
||||||
|
"change_department_name": "部门编辑",
|
||||||
|
"change_member_name": "成员改名",
|
||||||
"confirm_delete_from_org": "确认将 {{username}} 移出部门?",
|
"confirm_delete_from_org": "确认将 {{username}} 移出部门?",
|
||||||
"confirm_delete_from_team": "确认将 {{username}} 移出团队?",
|
"confirm_delete_from_team": "确认将 {{username}} 移出团队?",
|
||||||
"confirm_delete_group": "确认删除群组?",
|
"confirm_delete_group": "确认删除群组?",
|
||||||
@@ -12,13 +15,16 @@
|
|||||||
"confirm_forbidden": "确认停用",
|
"confirm_forbidden": "确认停用",
|
||||||
"confirm_leave_team": "确认离开该团队? \n退出后,您在该团队所有的资源均转让给团队所有者。",
|
"confirm_leave_team": "确认离开该团队? \n退出后,您在该团队所有的资源均转让给团队所有者。",
|
||||||
"copy_link": "复制链接",
|
"copy_link": "复制链接",
|
||||||
|
"create_department": "创建子部门",
|
||||||
"create_group": "创建群组",
|
"create_group": "创建群组",
|
||||||
"create_invitation_link": "创建邀请链接",
|
"create_invitation_link": "创建邀请链接",
|
||||||
"create_org": "创建部门",
|
"create_org": "创建部门",
|
||||||
"create_sub_org": "创建子部门",
|
"create_sub_org": "创建子部门",
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
|
"delete_department": "删除子部门",
|
||||||
"delete_from_org": "移出部门",
|
"delete_from_org": "移出部门",
|
||||||
"delete_from_team": "移出团队",
|
"delete_from_team": "移出团队",
|
||||||
|
"delete_group": "删除群组",
|
||||||
"delete_org": "删除部门",
|
"delete_org": "删除部门",
|
||||||
"edit_info": "编辑信息",
|
"edit_info": "编辑信息",
|
||||||
"edit_member": "编辑用户",
|
"edit_member": "编辑用户",
|
||||||
@@ -41,27 +47,37 @@
|
|||||||
"invitation_link_list": "链接列表",
|
"invitation_link_list": "链接列表",
|
||||||
"invite_member": "邀请成员",
|
"invite_member": "邀请成员",
|
||||||
"invited": "已邀请",
|
"invited": "已邀请",
|
||||||
|
"join_team": "加入团队",
|
||||||
"join_update_time": "加入/更新时间",
|
"join_update_time": "加入/更新时间",
|
||||||
|
"kick_out_team": "移除成员",
|
||||||
"label_sync": "标签同步",
|
"label_sync": "标签同步",
|
||||||
"leave": "已离职",
|
"leave": "已离职",
|
||||||
"leave_team_failed": "离开团队异常",
|
"leave_team_failed": "离开团队异常",
|
||||||
|
"log_details": "详情",
|
||||||
|
"log_time": "操作时间",
|
||||||
|
"log_type": "操作类型",
|
||||||
|
"log_user": "操作人员",
|
||||||
|
"login": "登录",
|
||||||
"manage_member": "管理成员",
|
"manage_member": "管理成员",
|
||||||
"member": "成员",
|
"member": "成员",
|
||||||
"member_group": "所属群组",
|
"member_group": "所属群组",
|
||||||
"move_member": "移动成员",
|
"move_member": "移动成员",
|
||||||
"move_org": "移动部门",
|
"move_org": "移动部门",
|
||||||
"notification_recieve": "团队通知接收",
|
"notification_recieve": "团队通知接收",
|
||||||
|
"operation_log": "日志",
|
||||||
"org": "部门",
|
"org": "部门",
|
||||||
"org_description": "介绍",
|
"org_description": "介绍",
|
||||||
"org_name": "部门名称",
|
"org_name": "部门名称",
|
||||||
"owner": "所有者",
|
"owner": "所有者",
|
||||||
"permission": "权限",
|
"permission": "权限",
|
||||||
"please_bind_contact": "请绑定联系方式",
|
"please_bind_contact": "请绑定联系方式",
|
||||||
|
"relocate_department": "部门移动",
|
||||||
"remark": "备注",
|
"remark": "备注",
|
||||||
"remove_tip": "确认将 {{username}} 移出团队?成员将被标记为“已离职”,不删除操作数据,账号下资源自动转让给团队所有者。",
|
"remove_tip": "确认将 {{username}} 移出团队?成员将被标记为“已离职”,不删除操作数据,账号下资源自动转让给团队所有者。",
|
||||||
"restore_tip": "确认将 {{username}} 加入团队吗?仅恢复该成员账号可用性及相关权限,无法恢复账号下资源。",
|
"restore_tip": "确认将 {{username}} 加入团队吗?仅恢复该成员账号可用性及相关权限,无法恢复账号下资源。",
|
||||||
"restore_tip_title": "恢复确认",
|
"restore_tip_title": "恢复确认",
|
||||||
"retain_admin_permissions": "保留管理员权限",
|
"retain_admin_permissions": "保留管理员权限",
|
||||||
|
"search_log": "搜索日志",
|
||||||
"search_member": "搜索成员",
|
"search_member": "搜索成员",
|
||||||
"search_member_group_name": "搜索成员/群组名称",
|
"search_member_group_name": "搜索成员/群组名称",
|
||||||
"search_org": "搜索部门",
|
"search_org": "搜索部门",
|
||||||
@@ -85,5 +101,17 @@
|
|||||||
"permission_datasetCreate_Tip": "可以在根目录创建知识库,(文件夹下的创建权限由文件夹控制)",
|
"permission_datasetCreate_Tip": "可以在根目录创建知识库,(文件夹下的创建权限由文件夹控制)",
|
||||||
"permission_apikeyCreate_Tip": "可以创建全局的 APIKey",
|
"permission_apikeyCreate_Tip": "可以创建全局的 APIKey",
|
||||||
"permission_manage": "管理员",
|
"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}}】]"
|
||||||
}
|
}
|
||||||
|
@@ -5,17 +5,23 @@
|
|||||||
"7days": "7 天",
|
"7days": "7 天",
|
||||||
"accept": "接受",
|
"accept": "接受",
|
||||||
"action": "操作",
|
"action": "操作",
|
||||||
|
"assign_permission": "權限變更",
|
||||||
|
"change_department_name": "部門編輯",
|
||||||
|
"change_member_name": "成員改名",
|
||||||
"confirm_delete_group": "確認刪除群組?",
|
"confirm_delete_group": "確認刪除群組?",
|
||||||
"confirm_delete_member": "確認刪除成員?",
|
"confirm_delete_member": "確認刪除成員?",
|
||||||
"confirm_delete_org": "確認刪除該部門?",
|
"confirm_delete_org": "確認刪除該部門?",
|
||||||
"confirm_forbidden": "確認停用",
|
"confirm_forbidden": "確認停用",
|
||||||
"confirm_leave_team": "確認離開該團隊? \n結束後,您在該團隊所有的資源轉讓給團隊所有者。",
|
"confirm_leave_team": "確認離開該團隊? \n結束後,您在該團隊所有的資源轉讓給團隊所有者。",
|
||||||
"copy_link": "複製連結",
|
"copy_link": "複製連結",
|
||||||
|
"create_department": "創建子部門",
|
||||||
"create_group": "建立群組",
|
"create_group": "建立群組",
|
||||||
"create_invitation_link": "建立邀請連結",
|
"create_invitation_link": "建立邀請連結",
|
||||||
"create_org": "建立部門",
|
"create_org": "建立部門",
|
||||||
"create_sub_org": "建立子部門",
|
"create_sub_org": "建立子部門",
|
||||||
"delete": "刪除",
|
"delete": "刪除",
|
||||||
|
"delete_department": "刪除子部門",
|
||||||
|
"delete_group": "刪除群組",
|
||||||
"delete_org": "刪除部門",
|
"delete_org": "刪除部門",
|
||||||
"edit_info": "編輯訊息",
|
"edit_info": "編輯訊息",
|
||||||
"edit_member": "編輯使用者",
|
"edit_member": "編輯使用者",
|
||||||
@@ -37,21 +43,51 @@
|
|||||||
"invitation_link_list": "連結列表",
|
"invitation_link_list": "連結列表",
|
||||||
"invite_member": "邀請成員",
|
"invite_member": "邀請成員",
|
||||||
"invited": "已邀請",
|
"invited": "已邀請",
|
||||||
|
"join_team": "加入團隊",
|
||||||
|
"kick_out_team": "移除成員",
|
||||||
"label_sync": "標籤同步",
|
"label_sync": "標籤同步",
|
||||||
"leave_team_failed": "離開團隊異常",
|
"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": "管理成員",
|
"manage_member": "管理成員",
|
||||||
"member": "成員",
|
"member": "成員",
|
||||||
"member_group": "所屬成員組",
|
"member_group": "所屬成員組",
|
||||||
"move_member": "移動成員",
|
"move_member": "移動成員",
|
||||||
"move_org": "行動部門",
|
"move_org": "行動部門",
|
||||||
|
"operation_log": "紀錄",
|
||||||
"org": "組織",
|
"org": "組織",
|
||||||
"org_description": "介紹",
|
"org_description": "介紹",
|
||||||
"org_name": "部門名稱",
|
"org_name": "部門名稱",
|
||||||
"owner": "擁有者",
|
"owner": "擁有者",
|
||||||
"permission": "權限",
|
"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": "備註",
|
"remark": "備註",
|
||||||
"remove_tip": "確認將 {{username}} 移出團隊?",
|
"remove_tip": "確認將 {{username}} 移出團隊?",
|
||||||
"retain_admin_permissions": "保留管理員權限",
|
"retain_admin_permissions": "保留管理員權限",
|
||||||
|
"search_log": "搜索日誌",
|
||||||
"search_member_group_name": "搜尋成員/群組名稱",
|
"search_member_group_name": "搜尋成員/群組名稱",
|
||||||
"total_team_members": "共 {{amount}} 名成員",
|
"total_team_members": "共 {{amount}} 名成員",
|
||||||
"transfer_ownership": "轉讓所有者",
|
"transfer_ownership": "轉讓所有者",
|
||||||
@@ -61,13 +97,5 @@
|
|||||||
"user_team_invite_member": "邀請成員",
|
"user_team_invite_member": "邀請成員",
|
||||||
"user_team_leave_team": "離開團隊",
|
"user_team_leave_team": "離開團隊",
|
||||||
"user_team_leave_team_failed": "離開團隊失敗",
|
"user_team_leave_team_failed": "離開團隊失敗",
|
||||||
"waiting": "待接受",
|
"waiting": "待接受"
|
||||||
"permission_appCreate": "建立應用程式",
|
|
||||||
"permission_datasetCreate": "建立知識庫",
|
|
||||||
"permission_apikeyCreate": "建立 API 密鑰",
|
|
||||||
"permission_appCreate_tip": "可以在根目錄建立應用程式,(資料夾下的建立權限由資料夾控制)",
|
|
||||||
"permission_datasetCreate_Tip": "可以在根目錄建立知識庫,(資料夾下的建立權限由資料夾控制)",
|
|
||||||
"permission_apikeyCreate_Tip": "可以建立全域的 APIKey",
|
|
||||||
"permission_manage": "管理員",
|
|
||||||
"permission_manage_tip": "可以管理成員、建立群組、管理所有群組、為群組和成員分配權限"
|
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ import {
|
|||||||
import { ChatBoxInputType, UserInputFileItemType } from './type';
|
import { ChatBoxInputType, UserInputFileItemType } from './type';
|
||||||
import { getFileIcon } from '@fastgpt/global/common/file/icon';
|
import { getFileIcon } from '@fastgpt/global/common/file/icon';
|
||||||
import { ChatItemValueTypeEnum, ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatItemValueTypeEnum, ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
|
import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||||
|
|
||||||
export const formatChatValue2InputType = (value?: ChatItemValueItemType[]): ChatBoxInputType => {
|
export const formatChatValue2InputType = (value?: ChatItemValueItemType[]): ChatBoxInputType => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@@ -82,17 +83,19 @@ export const setUserSelectResultToHistories = (
|
|||||||
i !== item.value.length - 1 ||
|
i !== item.value.length - 1 ||
|
||||||
val.type !== ChatItemValueTypeEnum.interactive ||
|
val.type !== ChatItemValueTypeEnum.interactive ||
|
||||||
!val.interactive
|
!val.interactive
|
||||||
)
|
) {
|
||||||
return val;
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
if (val.interactive.type === 'userSelect') {
|
const finalInteractive = extractDeepestInteractive(val.interactive);
|
||||||
|
if (finalInteractive.type === 'userSelect') {
|
||||||
return {
|
return {
|
||||||
...val,
|
...val,
|
||||||
interactive: {
|
interactive: {
|
||||||
...val.interactive,
|
...finalInteractive,
|
||||||
params: {
|
params: {
|
||||||
...val.interactive.params,
|
...finalInteractive.params,
|
||||||
userSelectedVal: val.interactive.params.userSelectOptions.find(
|
userSelectedVal: finalInteractive.params.userSelectOptions.find(
|
||||||
(item) => item.value === interactiveVal
|
(item) => item.value === interactiveVal
|
||||||
)?.value
|
)?.value
|
||||||
}
|
}
|
||||||
@@ -100,13 +103,13 @@ export const setUserSelectResultToHistories = (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (val.interactive.type === 'userInput') {
|
if (finalInteractive.type === 'userInput') {
|
||||||
return {
|
return {
|
||||||
...val,
|
...val,
|
||||||
interactive: {
|
interactive: {
|
||||||
...val.interactive,
|
...finalInteractive,
|
||||||
params: {
|
params: {
|
||||||
...val.interactive.params,
|
...finalInteractive.params,
|
||||||
submitted: true
|
submitted: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,7 @@ import { isEqual } from 'lodash';
|
|||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
|
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
|
||||||
import { SelectOptionsComponent, FormInputComponent } from './Interactive/InteractiveComponents';
|
import { SelectOptionsComponent, FormInputComponent } from './Interactive/InteractiveComponents';
|
||||||
|
import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||||
|
|
||||||
const accordionButtonStyle = {
|
const accordionButtonStyle = {
|
||||||
w: 'auto',
|
w: 'auto',
|
||||||
@@ -245,11 +246,12 @@ const AIResponseBox = ({
|
|||||||
return <RenderTool showAnimation={isChatting} tools={value.tools} />;
|
return <RenderTool showAnimation={isChatting} tools={value.tools} />;
|
||||||
}
|
}
|
||||||
if (value.type === ChatItemValueTypeEnum.interactive && value.interactive) {
|
if (value.type === ChatItemValueTypeEnum.interactive && value.interactive) {
|
||||||
if (value.interactive.type === 'userSelect') {
|
const finalInteractive = extractDeepestInteractive(value.interactive);
|
||||||
return <RenderUserSelectInteractive interactive={value.interactive} />;
|
if (finalInteractive.type === 'userSelect') {
|
||||||
|
return <RenderUserSelectInteractive interactive={finalInteractive} />;
|
||||||
}
|
}
|
||||||
if (value.interactive?.type === 'userInput') {
|
if (finalInteractive.type === 'userInput') {
|
||||||
return <RenderUserFormInteractive interactive={value.interactive} />;
|
return <RenderUserFormInteractive interactive={finalInteractive} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@@ -303,7 +303,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
})()}
|
})()}
|
||||||
</Td>
|
</Td>
|
||||||
<Td maxW={'300px'}>
|
<Td maxW={'300px'}>
|
||||||
<VStack gap={0}>
|
<VStack gap={0} align="start">
|
||||||
<Box>{format(new Date(member.createTime), 'yyyy-MM-dd HH:mm:ss')}</Box>
|
<Box>{format(new Date(member.createTime), 'yyyy-MM-dd HH:mm:ss')}</Box>
|
||||||
<Box>
|
<Box>
|
||||||
{member.updateTime
|
{member.updateTime
|
||||||
|
@@ -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<string>('');
|
||||||
|
const {
|
||||||
|
data: operationLogs = [],
|
||||||
|
isLoading: loadingLogs,
|
||||||
|
ScrollData: LogScrollData
|
||||||
|
} = useScrollPagination(getOperationLogs, {
|
||||||
|
pageSize: 20,
|
||||||
|
refreshDeps: [searchKey],
|
||||||
|
throttleWait: 500,
|
||||||
|
debounceWait: 200
|
||||||
|
});
|
||||||
|
|
||||||
|
const isLoading = loadingLogs;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
||||||
|
{Tabs}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<MyBox isLoading={isLoading} flex={'1 0 0'} overflow={'auto'}>
|
||||||
|
<LogScrollData>
|
||||||
|
<TableContainer overflow={'unset'} fontSize={'sm'}>
|
||||||
|
<Table overflow={'unset'}>
|
||||||
|
<Thead>
|
||||||
|
<Tr bgColor={'white !important'}>
|
||||||
|
<Th borderLeftRadius="6px" bgColor="myGray.100">
|
||||||
|
{t('account_team:log_user')}
|
||||||
|
</Th>
|
||||||
|
<Th bgColor="myGray.100">{t('account_team:log_time')}</Th>
|
||||||
|
<Th bgColor="myGray.100">{t('account_team:log_type')}</Th>
|
||||||
|
<Th bgColor="myGray.100">{t('account_team:log_details')}</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{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 ? (
|
||||||
|
<Tr key={log._id} overflow={'unset'}>
|
||||||
|
<Td>
|
||||||
|
<UserBox
|
||||||
|
sourceMember={log.sourceMember}
|
||||||
|
fontSize="sm"
|
||||||
|
avatarSize="1rem"
|
||||||
|
spacing={0.5}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
<Td>{formatTime2YMDHMS(log.timestamp)}</Td>
|
||||||
|
<Td>{t(i18nData.typeLabel)}</Td>
|
||||||
|
<Td>{t(i18nData.content, metadata as any) as string}</Td>
|
||||||
|
</Tr>
|
||||||
|
) : null;
|
||||||
|
})}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</LogScrollData>
|
||||||
|
</MyBox>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OperationLogTable;
|
@@ -13,7 +13,10 @@ import {
|
|||||||
SelectOptionsComponent
|
SelectOptionsComponent
|
||||||
} from '@/components/core/chat/components/Interactive/InteractiveComponents';
|
} from '@/components/core/chat/components/Interactive/InteractiveComponents';
|
||||||
import { UserInputInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
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 { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
|
|
||||||
@@ -130,10 +133,11 @@ const NodeDebugResponse = ({ nodeId, debugResult }: NodeDebugResponseProps) => {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const lastInteractive = getLastInteractiveValue(mockHistory);
|
||||||
onNextNodeDebug({
|
onNextNodeDebug({
|
||||||
...workflowDebugData,
|
...workflowDebugData,
|
||||||
// Rewrite runtimeEdges
|
// Rewrite runtimeEdges
|
||||||
runtimeEdges: initWorkflowEdgeStatus(workflowDebugData.runtimeEdges, mockHistory),
|
runtimeEdges: initWorkflowEdgeStatus(workflowDebugData.runtimeEdges, lastInteractive),
|
||||||
query: updatedQuery,
|
query: updatedQuery,
|
||||||
history: mockHistory
|
history: mockHistory
|
||||||
});
|
});
|
||||||
|
@@ -18,6 +18,7 @@ const MemberTable = dynamic(() => import('@/pageComponents/account/team/MemberTa
|
|||||||
const PermissionManage = dynamic(
|
const PermissionManage = dynamic(
|
||||||
() => import('@/pageComponents/account/team/PermissionManage/index')
|
() => 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 GroupManage = dynamic(() => import('@/pageComponents/account/team/GroupManage/index'));
|
||||||
const OrgManage = dynamic(() => import('@/pageComponents/account/team/OrgManage/index'));
|
const OrgManage = dynamic(() => import('@/pageComponents/account/team/OrgManage/index'));
|
||||||
const HandleInviteModal = dynamic(
|
const HandleInviteModal = dynamic(
|
||||||
@@ -28,7 +29,8 @@ export enum TeamTabEnum {
|
|||||||
member = 'member',
|
member = 'member',
|
||||||
org = 'org',
|
org = 'org',
|
||||||
group = 'group',
|
group = 'group',
|
||||||
permission = 'permission'
|
permission = 'permission',
|
||||||
|
operationLog = 'operationLog'
|
||||||
}
|
}
|
||||||
|
|
||||||
const Team = () => {
|
const Team = () => {
|
||||||
@@ -57,7 +59,8 @@ const Team = () => {
|
|||||||
{ label: t('account_team:member'), value: TeamTabEnum.member },
|
{ label: t('account_team:member'), value: TeamTabEnum.member },
|
||||||
{ label: t('account_team:org'), value: TeamTabEnum.org },
|
{ label: t('account_team:org'), value: TeamTabEnum.org },
|
||||||
{ label: t('account_team:group'), value: TeamTabEnum.group },
|
{ 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'}
|
px={'1rem'}
|
||||||
value={teamTab}
|
value={teamTab}
|
||||||
@@ -150,6 +153,7 @@ const Team = () => {
|
|||||||
{teamTab === TeamTabEnum.org && <OrgManage Tabs={Tabs} />}
|
{teamTab === TeamTabEnum.org && <OrgManage Tabs={Tabs} />}
|
||||||
{teamTab === TeamTabEnum.group && <GroupManage Tabs={Tabs} />}
|
{teamTab === TeamTabEnum.group && <GroupManage Tabs={Tabs} />}
|
||||||
{teamTab === TeamTabEnum.permission && <PermissionManage Tabs={Tabs} />}
|
{teamTab === TeamTabEnum.permission && <PermissionManage Tabs={Tabs} />}
|
||||||
|
{teamTab === TeamTabEnum.operationLog && <OperationLogTable Tabs={Tabs} />}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
{invitelinkid && <HandleInviteModal invitelinkid={invitelinkid} />}
|
{invitelinkid && <HandleInviteModal invitelinkid={invitelinkid} />}
|
||||||
|
@@ -98,7 +98,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
const isPlugin = app.type === AppTypeEnum.plugin;
|
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||||
|
|
||||||
const userQuestion: UserChatItemType = (() => {
|
const userQuestion: UserChatItemType = await (async () => {
|
||||||
if (isPlugin) {
|
if (isPlugin) {
|
||||||
return getPluginRunUserQuery({
|
return getPluginRunUserQuery({
|
||||||
pluginInputs: getPluginInputsFromStoreNodes(app.modules),
|
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) {
|
if (!latestHumanChat) {
|
||||||
throw new Error('User question is empty');
|
return Promise.reject('User question is empty');
|
||||||
}
|
}
|
||||||
return latestHumanChat;
|
return latestHumanChat;
|
||||||
})();
|
})();
|
||||||
@@ -136,14 +136,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newHistories = concatHistories(histories, chatMessages);
|
const newHistories = concatHistories(histories, chatMessages);
|
||||||
|
const interactive = getLastInteractiveValue(newHistories) || undefined;
|
||||||
// Get runtimeNodes
|
// Get runtimeNodes
|
||||||
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, newHistories));
|
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, interactive));
|
||||||
if (isPlugin) {
|
if (isPlugin) {
|
||||||
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
|
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
|
||||||
variables = {};
|
variables = {};
|
||||||
}
|
}
|
||||||
runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
|
runtimeNodes = rewriteNodeOutputByHistories(runtimeNodes, interactive);
|
||||||
|
|
||||||
const workflowResponseWrite = getWorkflowResponseWrite({
|
const workflowResponseWrite = getWorkflowResponseWrite({
|
||||||
res,
|
res,
|
||||||
@@ -175,9 +175,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
chatId,
|
chatId,
|
||||||
responseChatItemId,
|
responseChatItemId,
|
||||||
runtimeNodes,
|
runtimeNodes,
|
||||||
runtimeEdges: initWorkflowEdgeStatus(edges, newHistories),
|
runtimeEdges: initWorkflowEdgeStatus(edges, interactive),
|
||||||
variables,
|
variables,
|
||||||
query: removeEmptyUserInput(userQuestion.value),
|
query: removeEmptyUserInput(userQuestion.value),
|
||||||
|
lastInteractive: interactive,
|
||||||
chatConfig,
|
chatConfig,
|
||||||
histories: newHistories,
|
histories: newHistories,
|
||||||
stream: true,
|
stream: true,
|
||||||
|
@@ -10,6 +10,7 @@ import { NextAPI } from '@/service/middleware/entry';
|
|||||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
import { defaultApp } from '@/web/core/app/constants';
|
import { defaultApp } from '@/web/core/app/constants';
|
||||||
import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants';
|
import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants';
|
||||||
|
import { getLastInteractiveValue } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||||
|
|
||||||
async function handler(
|
async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
@@ -44,6 +45,7 @@ async function handler(
|
|||||||
|
|
||||||
// auth balance
|
// auth balance
|
||||||
const { timezone, externalProvider } = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
const { timezone, externalProvider } = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
||||||
|
const lastInteractive = getLastInteractiveValue(history);
|
||||||
|
|
||||||
/* start process */
|
/* start process */
|
||||||
const { flowUsages, flowResponses, debugResponse, newVariables, workflowInteractiveResponse } =
|
const { flowUsages, flowResponses, debugResponse, newVariables, workflowInteractiveResponse } =
|
||||||
@@ -65,6 +67,7 @@ async function handler(
|
|||||||
},
|
},
|
||||||
runtimeNodes: nodes,
|
runtimeNodes: nodes,
|
||||||
runtimeEdges: edges,
|
runtimeEdges: edges,
|
||||||
|
lastInteractive,
|
||||||
variables,
|
variables,
|
||||||
query: query,
|
query: query,
|
||||||
chatConfig: defaultApp.chatConfig,
|
chatConfig: defaultApp.chatConfig,
|
||||||
|
@@ -9,6 +9,8 @@ import { useIPFrequencyLimit } from '@fastgpt/service/common/middle/reqFrequency
|
|||||||
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
|
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
|
||||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||||
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
|
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) {
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const { username, password } = req.body as PostLoginProps;
|
const { username, password } = req.body as PostLoginProps;
|
||||||
@@ -64,6 +66,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
setCookie(res, token);
|
setCookie(res, token);
|
||||||
|
|
||||||
|
addOperationLog({
|
||||||
|
tmbId: userDetail.team.tmbId,
|
||||||
|
teamId: userDetail.team.teamId,
|
||||||
|
event: OperationLogEventEnum.LOGIN
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user: userDetail,
|
user: userDetail,
|
||||||
token
|
token
|
||||||
|
@@ -139,7 +139,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
// Computed start hook params
|
// Computed start hook params
|
||||||
const startHookText = (() => {
|
const startHookText = (() => {
|
||||||
// Chat
|
// 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;
|
if (userQuestion) return chatValue2RuntimePrompt(userQuestion.value).text;
|
||||||
|
|
||||||
// plugin
|
// plugin
|
||||||
@@ -245,16 +245,17 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
// Get chat histories
|
// Get chat histories
|
||||||
const newHistories = concatHistories(histories, chatMessages);
|
const newHistories = concatHistories(histories, chatMessages);
|
||||||
|
const interactive = getLastInteractiveValue(newHistories) || undefined;
|
||||||
|
|
||||||
// Get runtimeNodes
|
// Get runtimeNodes
|
||||||
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, newHistories));
|
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, interactive));
|
||||||
if (isPlugin) {
|
if (isPlugin) {
|
||||||
// Assign values to runtimeNodes using variables
|
// Assign values to runtimeNodes using variables
|
||||||
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
|
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
|
||||||
// Plugin runtime does not need global variables(It has been injected into the pluginInputNode)
|
// Plugin runtime does not need global variables(It has been injected into the pluginInputNode)
|
||||||
variables = {};
|
variables = {};
|
||||||
}
|
}
|
||||||
runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
|
runtimeNodes = rewriteNodeOutputByHistories(runtimeNodes, interactive);
|
||||||
|
|
||||||
const workflowResponseWrite = getWorkflowResponseWrite({
|
const workflowResponseWrite = getWorkflowResponseWrite({
|
||||||
res,
|
res,
|
||||||
@@ -288,7 +289,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
chatId,
|
chatId,
|
||||||
responseChatItemId,
|
responseChatItemId,
|
||||||
runtimeNodes,
|
runtimeNodes,
|
||||||
runtimeEdges: initWorkflowEdgeStatus(edges, newHistories),
|
runtimeEdges: initWorkflowEdgeStatus(edges, interactive),
|
||||||
variables,
|
variables,
|
||||||
query: removeEmptyUserInput(userQuestion.value),
|
query: removeEmptyUserInput(userQuestion.value),
|
||||||
chatConfig,
|
chatConfig,
|
||||||
|
@@ -139,7 +139,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
// Computed start hook params
|
// Computed start hook params
|
||||||
const startHookText = (() => {
|
const startHookText = (() => {
|
||||||
// Chat
|
// 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;
|
if (userQuestion) return chatValue2RuntimePrompt(userQuestion.value).text;
|
||||||
|
|
||||||
// plugin
|
// plugin
|
||||||
@@ -245,16 +245,16 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
// Get chat histories
|
// Get chat histories
|
||||||
const newHistories = concatHistories(histories, chatMessages);
|
const newHistories = concatHistories(histories, chatMessages);
|
||||||
|
const interactive = getLastInteractiveValue(newHistories) || undefined;
|
||||||
// Get runtimeNodes
|
// Get runtimeNodes
|
||||||
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, newHistories));
|
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, interactive));
|
||||||
if (isPlugin) {
|
if (isPlugin) {
|
||||||
// Assign values to runtimeNodes using variables
|
// Assign values to runtimeNodes using variables
|
||||||
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
|
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
|
||||||
// Plugin runtime does not need global variables(It has been injected into the pluginInputNode)
|
// Plugin runtime does not need global variables(It has been injected into the pluginInputNode)
|
||||||
variables = {};
|
variables = {};
|
||||||
}
|
}
|
||||||
runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
|
runtimeNodes = rewriteNodeOutputByHistories(runtimeNodes, interactive);
|
||||||
|
|
||||||
const workflowResponseWrite = getWorkflowResponseWrite({
|
const workflowResponseWrite = getWorkflowResponseWrite({
|
||||||
res,
|
res,
|
||||||
@@ -288,9 +288,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
chatId,
|
chatId,
|
||||||
responseChatItemId,
|
responseChatItemId,
|
||||||
runtimeNodes,
|
runtimeNodes,
|
||||||
runtimeEdges: initWorkflowEdgeStatus(edges, newHistories),
|
runtimeEdges: initWorkflowEdgeStatus(edges, interactive),
|
||||||
variables,
|
variables,
|
||||||
query: removeEmptyUserInput(userQuestion.value),
|
query: removeEmptyUserInput(userQuestion.value),
|
||||||
|
lastInteractive: interactive,
|
||||||
chatConfig,
|
chatConfig,
|
||||||
histories: newHistories,
|
histories: newHistories,
|
||||||
stream,
|
stream,
|
||||||
|
@@ -33,9 +33,21 @@ const reduceQueue = () => {
|
|||||||
|
|
||||||
return global.qaQueueLen === 0;
|
return global.qaQueueLen === 0;
|
||||||
};
|
};
|
||||||
|
const reduceQueueAndReturn = (delay = 0) => {
|
||||||
|
reduceQueue();
|
||||||
|
if (delay) {
|
||||||
|
setTimeout(() => {
|
||||||
|
generateQA();
|
||||||
|
}, delay);
|
||||||
|
} else {
|
||||||
|
generateQA();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export async function generateQA(): Promise<any> {
|
export async function generateQA(): Promise<any> {
|
||||||
const max = global.systemEnv?.qaMaxProcess || 10;
|
const max = global.systemEnv?.qaMaxProcess || 10;
|
||||||
|
addLog.debug(`[QA Queue] Queue size: ${global.qaQueueLen}`);
|
||||||
|
|
||||||
if (global.qaQueueLen >= max) return;
|
if (global.qaQueueLen >= max) return;
|
||||||
global.qaQueueLen++;
|
global.qaQueueLen++;
|
||||||
|
|
||||||
@@ -98,14 +110,12 @@ export async function generateQA(): Promise<any> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (error) {
|
if (error) {
|
||||||
reduceQueue();
|
return reduceQueueAndReturn();
|
||||||
return generateQA();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// auth balance
|
// auth balance
|
||||||
if (!(await checkTeamAiPointsAndLock(data.teamId))) {
|
if (!(await checkTeamAiPointsAndLock(data.teamId))) {
|
||||||
reduceQueue();
|
return reduceQueueAndReturn();
|
||||||
return generateQA();
|
|
||||||
}
|
}
|
||||||
addLog.info(`[QA Queue] Start`);
|
addLog.info(`[QA Queue] Start`);
|
||||||
|
|
||||||
@@ -137,14 +147,8 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
|
|||||||
|
|
||||||
const qaArr = formatSplitText({ answer, rawText: text, llmModel: modelData }); // 格式化后的QA对
|
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
|
// get vector and insert
|
||||||
const { insertLen } = await pushDataListToTrainingQueueByCollectionId({
|
await pushDataListToTrainingQueueByCollectionId({
|
||||||
teamId: data.teamId,
|
teamId: data.teamId,
|
||||||
tmbId: data.tmbId,
|
tmbId: data.tmbId,
|
||||||
collectionId: data.collectionId,
|
collectionId: data.collectionId,
|
||||||
@@ -160,21 +164,21 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
|
|||||||
await MongoDatasetTraining.findByIdAndDelete(data._id);
|
await MongoDatasetTraining.findByIdAndDelete(data._id);
|
||||||
|
|
||||||
// add bill
|
// add bill
|
||||||
if (insertLen > 0) {
|
pushQAUsage({
|
||||||
pushQAUsage({
|
teamId: data.teamId,
|
||||||
teamId: data.teamId,
|
tmbId: data.tmbId,
|
||||||
tmbId: data.tmbId,
|
inputTokens: await countGptMessagesTokens(messages),
|
||||||
inputTokens: await countGptMessagesTokens(messages),
|
outputTokens: await countPromptTokens(answer),
|
||||||
outputTokens: await countPromptTokens(answer),
|
billId: data.billId,
|
||||||
billId: data.billId,
|
model: modelData.model
|
||||||
model: modelData.model
|
});
|
||||||
});
|
addLog.info(`[QA Queue] Finish`, {
|
||||||
} else {
|
time: Date.now() - startTime,
|
||||||
addLog.info(`QA result 0:`, { answer });
|
splitLength: qaArr.length,
|
||||||
}
|
usage: chatResponse.usage
|
||||||
|
});
|
||||||
|
|
||||||
reduceQueue();
|
return reduceQueueAndReturn();
|
||||||
generateQA();
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
addLog.error(`[QA Queue] Error`, err);
|
addLog.error(`[QA Queue] Error`, err);
|
||||||
await MongoDatasetTraining.updateOne(
|
await MongoDatasetTraining.updateOne(
|
||||||
@@ -188,9 +192,7 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
setTimeout(() => {
|
return reduceQueueAndReturn(1000);
|
||||||
generateQA();
|
|
||||||
}, 1000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -35,6 +35,8 @@ const reduceQueueAndReturn = (delay = 0) => {
|
|||||||
/* 索引生成队列。每导入一次,就是一个单独的线程 */
|
/* 索引生成队列。每导入一次,就是一个单独的线程 */
|
||||||
export async function generateVector(): Promise<any> {
|
export async function generateVector(): Promise<any> {
|
||||||
const max = global.systemEnv?.vectorMaxProcess || 10;
|
const max = global.systemEnv?.vectorMaxProcess || 10;
|
||||||
|
addLog.debug(`[Vector Queue] Queue size: ${global.vectorQueueLen}`);
|
||||||
|
|
||||||
if (global.vectorQueueLen >= max) return;
|
if (global.vectorQueueLen >= max) return;
|
||||||
global.vectorQueueLen++;
|
global.vectorQueueLen++;
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
@@ -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<PaginationProps>) =>
|
||||||
|
POST<PaginationResponse<OperationListItemType>>(
|
||||||
|
`/proApi/support/user/team/operationLog/list`,
|
||||||
|
props
|
||||||
|
);
|
Reference in New Issue
Block a user